一、spring概述
1.1、spring是什么
? Spring 是分层的 Java SE/EE 应用 full-stack 轻量级开源框架,以 IoC(Inverse Of Control: 反转控制)和 AOP(Aspect Oriented Programming:面向切面编程)为内核,提供了展现层 Spring MVC 和持久层 Spring JDBC 以及业务层事务管理等众多的企业级应用技术,还能整合开源世界众多 著名的第三方框架和类库,逐渐成为使用最多的 Java EE 企业应用开源框架。
1.2、spring的体系结构
二、IOC的概念和作用
2.1、耦合和依赖
? 耦合性(Coupling),也叫耦合度,是对模块间关联程度的度量。耦合的强弱取决于模块间接口的复杂性、调 用模块的方式以及通过界面传送数据的多少。模块间的耦合度是指模块之间的依赖关系,包括控制关系、调用关 系、数据传递关系。模块间联系越多,其耦合性越强,同时表明其独立性越差( 降低耦合性,可以提高其独立 性)。耦合性存在于各个领域,而非软件设计中独有的,但是我们只讨论软件工程中的耦合。 在软件工程中,耦合指的就是就是对象之间的依赖性。对象之间的耦合越高,维护成本越高。因此对象的设计 应使类和构件之间的耦合最小。软件设计中通常用耦合度和内聚度作为衡量模块独立程度的标准。划分模块的一个 准则就是高内聚低耦合。
? 这里用数据库连接作为例子讲解。
public class JDBCDemo1 {
public static void main(String[] args) {
Connection connection;
PreparedStatement preparedStatement;
ResultSet resultSet;
try {
//尽量使用com.mysql.cj.jdbc.Driver注册驱动,第一种注册驱动方式
//如果没有该jar包,该方式会在编译期时报错
//DriverManager.registerDriver(new com.mysql.cj.jdbc.Driver());
//第二种注册驱动方式,如果没有mysql驱动,在编译期时并不会报异常,只会在运行期时报异常
Class.forName("com.mysql.cj.jdbc.Driver");
//使用三个参数的构造方法获取连接对象
connection = DriverManager.getConnection("jdbc:mysql://39.99.241.91:3306/mybatis","root","root");
String sql = "select * from account";
//使用该执行对象,防止sql注入
preparedStatement = connection.prepareStatement(sql);
//获取查询结果集
resultSet = preparedStatement.executeQuery();
while (resultSet.next()){
System.out.println(resultSet.getString("id"));
System.out.println(resultSet.getString("uid"));
System.out.println(resultSet.getString("money"));
}
} catch (SQLException | ClassNotFoundException e) {
e.printStackTrace();
}
}
}
? 从上面的例子可以看出,如果采用DriverManager.registerDriver(new com.mysql.cj.jdbc.Driver())方式注册驱动,在没有导入数据库相应的jar包之前,IDEA第一时间会报错,也就是编译时错误,找不到对应的类,这样就造成了编译时依赖。而当我们使用java反射的方式,如Class.forName("com.mysql.cj.jdbc.Driver")注册驱动时,如果没有对应的jar包,是不会在编译时刻出现错误,只会在运行时抛出异常(找不到对应的驱动)。
? 这样又会产生一个问题,这里咱们采用的是mysql的驱动,如果需要换成oracle的数据库驱动,我们需要在源码中进行修改,这也是很麻烦的。
2.2、工厂模式解耦
? 在实际开发中我们可以把三层的对象都使用配置文件配置起来,当启动服务器应用加载的时候,让一个类中的 方法通过读取配置文件,把这些对象创建出来并存起来。在接下来的使用的时候,直接拿过来用就好了。 那么,这个读取配置文件,创建和获取三层对象的类就是工厂。
? 首先是配置文件,这里选择.properties后缀的文件
//bean.xml
accountService=com.touwowo.service.impl.AccountServiceImpl
accountDao=com.touwowo.dao.impl.AccountDaoImpl
? 然后是bean工厂代码的编写,其中javabean的意思就是用java语言编写的可重用组件
public class BeanFactory {
private static Properties properties;
private static Map<String,Object> beans;
static {
//getClassLoader().getResourceAsStream会加载target目录下classes目录下的配置文件
InputStream resourceAsStream = BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties");
properties = new Properties();
try {
properties.load(resourceAsStream);
beans = new HashMap<>();
//饿汉式加载所有对象
Set<Object> objects = properties.keySet();
for (Object object : objects) {
String key = (String)object.toString();
String value = properties.getProperty(key);
Object temp = Class.forName(value).newInstance();
beans.put(key,temp);
}
} catch (Exception e) {
e.printStackTrace();
}
}
public static Object getBean(String className){
String property = properties.getProperty(className);
if(beans.containsKey(property)){
return beans.get(property);
}
Object bean = null;
try {
bean = Class.forName(property).newInstance();
beans.put(property,bean);
} catch (InstantiationException | IllegalAccessException | ClassNotFoundException e) {
e.printStackTrace();
}
return bean;
}
}
? 工厂的本质就是一个容器,所以我选择使用hashmap作为对象的容器。创建bean对象时可以采用饿汉式或者懒汉式进行对象的创建,看自己的需求而定。可以看到,BeanFactory中创建对象均是采用反射创建对象,避免new关键字造成程序的耦合。
? 后续创建对象:
AccountDao accountDao = (AccountDao) BeanFactory.getBean("accountDao");
通过以上方式,就可以降低程序之间的耦合,而spring正是为我们提供了一个简便的方式降低耦合并且增加了许多功能。
2.3、使用spring的IOC(控制反转-Inversion Of Control)
首先了解IOC的概念:
? 控制反转(Inversion of Control,缩写为IoC),是面向对象编程中的一种设计原则,可以用来减低计算机代码之间的耦合度。其中最常见的方式叫做依赖注入(Dependency Injection,简称DI),还有一种方式叫“依赖查找”(Dependency Lookup)。通过控制反转,对象在被创建的时候,由一个调控系统内所有对象的外界实体(这谁写的,这是什么实体?应该是Core的进程、线程、对象、组件?另外“实体”他的修饰词,内部和外界的划分不应该以创建和被创建为参照物来定义,而是应该以core为界线来定义,这样修饰词就可以称为“内部”),将其所依赖的对象的引用传递(注入)给它。
? 如果还不怎么理解,这里有一张图帮助理解。
?
? 不难看出,上个例子中的BeanFactory就是提供了管理javabean的工厂,创建对象的控制权给到了BeanFactory,而不是用户。
现在学习spring的IOC,第一步导入spring的依赖。
<dependencies>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.9.RELEASE</version>
</dependency>
</dependencies>
第二步在Resources目录创建后缀为.xml的文件。这里就是spring的配置文件,必须导入配置文件的约束,如下。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
</beans>
第三步让spring管理资源,所以需要在配置文件中配置咱们的service层和dao层的实现类。
<!--
bean 标签:用于配置让 spring 创建对象,并且存入 ioc 容器之中
id 属性:对象的唯一标识。
class 属性:指定要创建对象的全限定类名
-->
<bean id="accountService" class="com.touwowo.service.impl.AccountServiceImpl"/>
<bean id="accountDao" class="com.touwowo.dao.impl.AccountDaoImpl"/>
第四步测试配置是否成功。这里使用带有main方法的类进行测试,模拟
public class Client {
/**
* 获取spring的IOC容器,并根据ID查看对象
* 1.ApplicationContext:使用该方式采用立即加载的策略
* 2.BeanFactory:懒加载策略
* @param args
*/
public static void main(String[] args) {
/*
//1.获取核心容器对象,加载配置文件
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("bean.xml");
//2.根据ID获取bean对象
AccountService accountService = applicationContext.getBean("accountService", AccountService.class);
AccountDao accountDao = applicationContext.getBean("accountDao", AccountDao.class);
*/
//这里的BeanFactory不是我们写的BeanFactory,而是spring提供的
BeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("bean.xml"));
AccountService accountService = beanFactory.getBean("accountService", AccountService.class);
AccountDao accountDao = beanFactory.getBean("accountDao", AccountDao.class);
//这里需要强制转换
AccountDao accountDao1 = (AccountDao) beanFactory.getBean("accountDao");
System.out.println(accountService);
System.out.println(accountDao);
}
}
? getbean()的第二个参数为xxx.class,作用是避免强制转换(我懒)。
2.4、扩展
2.4.1、 BeanFactory 和 ApplicationContext 的区别
BeanFactory 才是 Spring 容器中的顶层接口。
ApplicationContext 是它的子接口。
BeanFactory 和 ApplicationContext 的区别:创建对象的时间点不一样。
ApplicationContext:只要一读取配置文件,默认情况下就会创建对象。
BeanFactory:什么使用什么时候创建对象。
2.4.1、ApplicationContext 接口的实现类
ClassPathXmlApplicationContext:它是从类的根路径下加载配置文件 推荐使用这种
FileSystemXmlApplicationContext:它是从磁盘路径上加载配置文件,配置文件可以在磁盘的任意位置。
AnnotationConfigApplicationContext:当我们使用注解配置容器对象时,需要使用此类来创建 spring 容器。它用来读取注解。
2.5、IOC中配置文件的编写以及管理对象的细节
2.5.1、bean标签
1、作用:
? 用于配置对象让 spring 来创建的。 默认情况下它调用的是类中的无参构造函数。如果没有无参构造函数则不能创建成功。
2、属性:
id:给对象在容器中提供一个唯一标识。用于获取对象。
class:指定类的全限定类名。用于反射创建对象。默认情况下调用无参构造函数。
scope:指定对象的作用范围。
- singleton :默认值,单例的.
- prototype :多例的.
- request :WEB 项目中,Spring 创建一个 Bean 的对象,将对象存入到 request 域中.
- session :WEB 项目中,Spring 创建一个 Bean 的对象,将对象存入到 session 域中.
- global session :WEB 项目中,应用在 Portlet 环境.如果没有 Portlet 环境那么 globalSession 相当于 session.
init-method:指定类中的初始化方法名称。
destroy-method:指定类中销毁方法名称
3.作用范围以及生命周期
单例对象:scope="singleton"
一个应用只有一个对象的实例。它的作用范围就是整个引用。
生命周期: 对象出生:当应用加载,创建容器时,对象就被创建了。
对象活着:只要容器在,对象一直活着。
对象死亡:当应用卸载,销毁容器时,对象就被销毁了。
多例对象:scope="prototype"
每次访问对象时,都会重新创建对象实例。
生命周期: 对象出生:当使用对象时,创建新的对象实例。
对象活着:只要对象在使用中,就一直活着。
对象死亡:当对象长时间不用时,被 java 的垃圾回收器回收了。
4、实例化Bean的三种方式
-
第一种:使用默认无参构造函数
其中bean.xml加入以下代码。
<!-- 第一种:使用使用默认构造函数创建,即最简单的方式.如果没找到默认构造函数,则配置文件报错 --> <!-- 默认采用单例模式,立即加载策略 --> <bean id="accountService" class="com.touwowo.service.impl.AccountServiceImpl"/>
-
第二种:spring 管理实例工厂——使用实例工厂的方法创建对象
<!-- 第二种,使用某个类中的方法创建对象,并存入spring容器 --> <!-- 1.先创建工厂类的bean对象,然后才能使用工厂创建的对象 --> <bean id="instanceFactory" class="com.touwowo.factory.instanceFactory"/> <bean id="accountServiceInFactory" factory-bean="instanceFactory" factory-method="getAccountService"/>
instanceFactory.java源代码:
public class InstanceFactory { public AccountService getAccountService(){ return new AccountServiceImpl(); } }
-
第三种:spring 管理静态工厂——使用静态工厂的方法创建对象
<!-- 使用某个类中的静态方法创建对象,并存入spring容器 --> <bean id="accountServiceInStatic" class="com.touwowo.factory.StaticFactory" factory-method="getAccountService"/>
StaticFactory源代码:
public class StaticFactory { public static AccountService getAccountService(){ return new AccountServiceImpl(); } }
2.6、spring的依赖注入
1、依赖注入的概念
依赖注入:Dependency Injection。它是 spring 框架核心 ioc 的具体实现。
2、构造函数注入
首先创建一个名为UserTest1的实体类,其中只包含有参构造函数。
public class UserTest1 {
private String name;
private Integer age;
private Date birthday;
public UserTest1(String name, Integer age, Date birthday) {
this.name = name;
this.age = age;
this.birthday = birthday;
}
}
bean.xml加入如下代码:
<bean id="userTest1" class="com.touwowo.pojo.UserTest1">
<constructor-arg name="name" value="窝窝头"/>
<constructor-arg name="age" value="19"/>
<!-- 如果需要使用非基本类型和String类型,需要引入其他bean,如Date -->
<constructor-arg name="birthday" ref="date"/>
</bean>
<bean id="date" class="java.util.Date"/>
constructor-arg的属性及其用法
index:指定参数在构造函数参数列表的索引位置
type:指定参数在构造函数中的数据类型
name:指定参数在构造函数中的名称 用这个找给谁赋值
=======上面三个都是找给谁赋值,下面两个指的是赋什么值的==============
value:它能赋的值是基本数据类型和 String 类型
ref:它能赋的值是其他 bean 类型,也就是说,必须得是在配置文件中配置过的 bean
3、setter方法注入
首先创建名为UserTest2的实体类,并加入setter方法
public class UserTest2 {
private String name;
private Integer age;
private Date birthday;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
}
bean.xml加入以下代码:
<!-- setter方法注入 -->
<bean id="userTest2" class="com.touwowo.pojo.UserTest2">
<property name="name" value="烤山药"/>
<property name="age" value="18"/>
<property name="birthday" ref="date"/>
</bean>
property标签的属性及其用法:
name:找的是类中 set 方法后面的部分
ref:给属性赋值是其他 bean 类型的
value:给属性赋值是基本数据类型和 string 类型的
实际开发中,此种方式用的较多。
4、注入集合类型
创建CollectionDI实体类
public class CollectionDI {
private String[] myStr;
private List<String> myList;
private Set<String> mySet;
private Map<String,Object> myMap;
private Properties properties;
//使用setter方法注入,省略setter方法
}
bean.xml加入以下代码
<!-- 常用集合注入 -->
<bean id="collection" class="com.touwowo.pojo.CollectionDI">
<property name="myStr">
<array>
<value>1</value>
<value>2</value>
<value>3</value>
</array>
</property>
<property name="myList">
<list>
<value>4</value>
<value>5</value>
<value>6</value>
</list>
</property>
<property name="mySet">
<set>
<value>7</value>
<value>7</value>
<value>7</value>
</set>
</property>
<property name="myMap">
<map>
<entry key="name" value="窝窝头"/>
<entry key="age" value="18"/>
<entry key="birthday" value-ref="date"/>
</map>
</property>
<property name="properties">
<props>
<prop key="name">烤山药</prop>
<prop key="age">18</prop>
</props>
</property>
</bean>
5、实际开发中使用
使用场景:业务层需要调用数据层的方法。
public class AccountServiceImpl implements AccountService {
private AccountDao accountDao;
public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}
}
如果我们需要将AccountDao的实现类注入到AccountService的实现类中,可以选择构造方法注入或者setter方法注入,我这里选择setter方法进行注入。
bean.xml文件的编写
<bean id="accountService" class="com.touwowo.service.impl.AccountServiceImpl">
<property name="accountDao" ref="accountDao"/>
</bean>
<bean id="accountDao" class="com.touwowo.dao.impl.AccountDaoImpl"/>
这样就能实现在业务层中注入数据层的实现类。
2.7、基于注解的IOC配置
1、在配置文件中开启对注解的支持
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 基于注解实现注册bean,管理bean -->
<!-- base-package是需要扫描注解的包 -->
<context:component-scan base-package="com.touwowo"/>
</beans>
2、常用注解
@Component
作用:
把资源让 spring 来管理。相当于在 xml 中配置一个 bean。
属性:
value:指定 bean 的 id。如果不指定 value 属性,默认 bean 的 id 是当前类的类名。首字母小写。
@Controller @Service @Repository
他们三个注解都是针对一个的衍生注解,他们的作用及属性都是一模一样的。
他们只不过是提供了更加明确的语义化。
@Controller:一般用于表现层的注解。
@Service:一般用于业务层的注解。
@Repository:一般用于持久层的注解。
细节:如果注解中有且只有一个属性要赋值时,且名称是 value,value 在赋值是可以不写。
@Autowired
作用:
自动按照类型注入。当使用注解注入属性时,set 方法可以省略。它只能注入其他 bean 类型。当有多个类型匹配时,使用要注入的对象变量名称作为 bean 的 id,在 spring 容器查找,找到了也可以注入成功。找不到就报错。
@Qualifier
作用:
在自动按照类型注入的基础之上,再按照 Bean 的 id 注入。它在给字段注入时不能独立使用,必须和@Autowire 一起使用;但是给方法参数注入时,可以独立使用。
属性:
value:指定 bean 的 id。
@Resource
作用:
直接按照 Bean 的 id 注入。它也只能注入其他 bean 类型。
属性:
name:指定 bean 的 id。
@Value
作用:
注入基本数据类型和 String 类型数据的
属性:
value:用于指定值
@Scope
作用:
指定 bean 的作用范围。
属性:
value:指定范围的值。
取值:singleton prototype request session globalsession
@PostConstruct
作用:
用于指定初始化方法。
@PreDestroy
作用:
用于指定销毁方法。
spring整合Juint
1、导入依赖
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.11.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.2.11.RELEASE</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.16</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.10</version>
</dependency>
<!-- 整合必备依赖 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.0.6.RELEASE</version>
<scope>test</scope>
</dependency>
</dependencies>
这里我采用数据库连接进行一个测试,顺带使用JdbcTemplate操作数据库
2、修改测试类
通过 @RunWith 注解,指定 Spring 的运行器,这里 Spring的运行器是SpringJunit4ClassRunner
通过 @ContextConfiguration 注解,指定 Spring 运行器需要的配置文件路径或者配置类,这里由于我们使用的全注解的方式,所以指定的是配置类。
Test.java源代码:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:bean.xml"})
public class test {
}
改造以后就可以在测试类中使用@Autowired等注解注入数据!
3、编写bean.xml配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="com.touwowo"/>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<constructor-arg type="javax.sql.DataSource" ref="dataSource"/>
</bean>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://39.98.121.124:3306/spring_study"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</bean>
</beans>
4、测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:bean.xml"})
public class test {
@Autowired
private JdbcTemplate jdbcTemplate;
@Test
public void test1(){
List query = jdbcTemplate.query("select * from account", new BeanPropertyRowMapper(Account.class));
System.out.println(query);
System.out.println(jdbcTemplate);
}
}
输出结果:
[Account{id=1, uid=1, money=2000.0}, Account{id=2, uid=2, money=3000.0}]
整合成功!
三、AOP的概念和作用
3.1、引入案例
转账的案例:当一个账户向另一个账户进行转账操作时,在转出账户更新之后,抛出异常,则转入账户则不会更新,这违背了事务的一致性(部分代码如下)
/**
*
* @param sourceId 转出账户ID
* @param targetId 转入账户ID
* @param money 金额
*/
@Override
public void transferAccount(Integer sourceId, Integer targetId, Double money) {
Account sourceAccount = accountDao.getAccountById(sourceId); //根据账户id获取账户对象
Account targetAccount = accountDao.getAccountById(targetId);
sourceAccount.setMoney(sourceAccount.getMoney()-money);
targetAccount.setMoney(targetAccount.getMoney()+money);
accountDao.updateAccount(sourceAccount); //更新账户操作
//int i = 1/0; //如果此处抛出异常,转入账户不能有效更新
accountDao.updateAccount(targetAccount);
}
分析问题产生的原因:
因为转账方法中,需要获取四个连接,并且每个连接不是相同的,第一步操作就是使用ThreadLocal将线程和连接绑定,保证一个线程中只有一个能控制事务的连接。
第一步,创建ConnectionUtils工具类,实现对线程和连接的绑定,保证只有一个连接。
/**
* 连接的工具类,它用于从数据源中获取一个连接,并且实现和线程的绑定
*/
@Component("connectionUtils")
public class ConnectionUtils {
private ThreadLocal<Connection> tl = new ThreadLocal<Connection>();
@Autowired
private DataSource dataSource;
/**
* 获取当前线程上的连接
* @return
*/
public Connection getThreadConnection() {
try{
//1.先从ThreadLocal上获取
Connection conn = tl.get();
//2.判断当前线程上是否有连接
if (conn == null) {
//3.从数据源中获取一个连接,并且存入ThreadLocal中
conn = dataSource.getConnection();
tl.set(conn);
}
//4.返回当前线程上的连接
return conn;
}catch (Exception e){
throw new RuntimeException(e);
}
}
/**
* 把连接和线程解绑
*/
public void removeConnection(){
tl.remove();
}
}
扩展:
第二步,实现对事务的控制。
/**
* 和事务管理相关的工具类,它包含了,开启事务,提交事务,回滚事务和释放连接
*/
@Component("transactionManager")
public class TransactionManager {
@Autowired //使用自动注入
private ConnectionUtils connectionUtils;
/**
* 开启事务
*/
public void beginTransaction(){
try {
connectionUtils.getThreadConnection().setAutoCommit(false);
}catch (Exception e){
e.printStackTrace();
}
}
/**
* 提交事务
*/
public void commit(){
try {
connectionUtils.getThreadConnection().commit();
}catch (Exception e){
e.printStackTrace();
}
}
/**
* 回滚事务
*/
public void rollback(){
try {
connectionUtils.getThreadConnection().rollback();
}catch (Exception e){
e.printStackTrace();
}
}
/**
* 释放连接
*/
public void release(){
try {
connectionUtils.getThreadConnection().close();//还回连接池中
connectionUtils.removeConnection();
}catch (Exception e){
e.printStackTrace();
}
}
}
第三步、在业务层实现事务控制。
@Service("accountService")
public class AccountServiceImpl implements IAccountService{
@Autowired
private IAccountDao accountDao;
@Autowired
private TransactionManager txManager;
//省略一些业务层方法,主要是转账方法
@Override
public void transfer(Integer sourceId, Integer targetId, Double money) {
try {
//1.开启事务
txManager.beginTransaction();
//2.执行操作
//2.1根据名称查询转出账户
Account source = accountDao.getAccountById(sourceId);
//2.2根据名称查询转入账户
Account target = accountDao.getAccountById(targetId);
//2.3转出账户减钱
source.setMoney(source.getMoney()-money);
//2.4转入账户加钱
target.setMoney(target.getMoney()+money);
//2.5更新转出账户
accountDao.updateAccount(source);
int i=1/0;
//2.6更新转入账户
accountDao.updateAccount(target);
//3.提交事务
txManager.commit();
}catch (Exception e){
//4.回滚操作
txManager.rollback();
e.printStackTrace();
}finally {
//5.释放连接,详细操作请看TransactionManager.java
txManager.release();
}
}
}
第四步,所有的持久层方法的connection都应该从ConnectionUtils中获取,保证控制事务的连接只有一个
@Repository("accountDao")
public class AccountDaoImpl implements IAccountDao {
@Autowired
private QueryRunner runner;
@Autowired
private ConnectionUtils connectionUtils;
@Override
public Account findAccountById(Integer accountId) {
try{
//使用connectionUtils.getThreadConnection()方法获取ThreadLcoal中的连接
return runner.query(connectionUtils.getThreadConnection(),"select * from account where id = ? ",new BeanHandler<Account>(Account.class),accountId);
}catch (Exception e) {
throw new RuntimeException(e);
}
}
}
第五步,创建bean.xml实现对注解的支持以及DataSource的配置 ,(简简单单,就不复制过来了)
3.2、案例总结
以上对业务层的改造,能实现对转账方法的事务控制,但是存在一些问题,那就是代码冗余。如果我们需要对业务层的每个方法进行事务控制,就需要写大量的重复代码。况且如果以后修改ConnectionUtils或者TxManager的代码,需要修改很多地方,对日常开发非常的不方便。代理模式能很好的解决此类问题,减少冗余的代码。
3.3、代理模式
代理模式是一种设计模式,提供了对目标对象额外的访问方式,即通过代理对象访问目标对象,这样可以在不修改原目标对象的前提下,提供额外的功能操作,扩展目标对象的功能。
简言之,代理模式就是设置一个中间代理来控制访问原目标对象,以达到增强原对象的功能和简化访问方式。
3.3.1、静态代理模式
这种代理方式需要代理对象和目标对象实现一样的接口。
优点:可以在不修改目标对象的前提下扩展目标对象的功能。
缺点:
- 冗余。由于代理对象要实现与目标对象一致的接口,会产生过多的代理类。
- 不易维护。一旦接口增加方法,目标对象与代理对象都要进行修改。
举例:
- 接口类AccountService
public interface AccountService {
void transferAccount(Integer sourceId,Integer targetId,Double money);
}
- 接口实现类AccountServiceImpl
@Service("accountService")
public class AccountServiceImpl implements IAccountService{
@Autowired
private IAccountDao accountDao;
@Override
public void transfer(Integer sourceId, Integer targetId, Double money) {
//省略业务流程,如上
}
}
- 静态代理类AccountServiceProxy
public class AccountServiceProxy implements IAccountService{
@Autowired //使用自动注入
@Qualifier("accountService")
private AccountService accountService;
@@Autowired
private TransactionManager txManager;
@Override
public void transfer(Integer sourceId, Integer targetId, Double money) {
try {
txManager.beginTransaction();
//业务流程
txManager.commit();
}catch (Exception e){
txManager.rollback();
e.printStackTrace();
}finally {
txManager.release();
}
}
}
注:使用时就要使用静态代理类AccountServiceProxy,而不是接口实现类AccountServiceImpl
3.3.2、动态代理之JDK代理
动态代理利用了JDK API,动态地在内存中构建代理对象,从而实现对目标对象的代理功能。动态代理又被称为JDK代理或接口代理。
静态代理与动态代理的区别主要在:
- 静态代理在编译时就已经实现,编译完成后代理类是一个实际的class文件
- 动态代理是在运行时动态生成的,即编译完成后没有实际的class文件,而是在运行时动态生成类字节码,并加载到JVM中
特点:
动态代理对象不需要实现接口,但是要求目标对象必须实现接口,否则不能使用动态代理。
- java.lang.reflect Proxy,主要方法为
static Object newProxyInstance(
ClassLoader loader, //指定当前目标对象使用类加载器
Class<?>[] interfaces, //目标对象实现的接口的类型
InvocationHandler h //事件处理器
)
//返回一个指定接口的代理类实例,该接口可以将方法调用指派到指定的调用处理程序。
Object invoke(Object proxy, Method method, Object[] args)
// 在代理实例上处理方法调用并返回结果。
- 动态代理对象:ProducerProxy
package com.touwowo.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class ProducerProxy {
private Object target;
public ProducerProxy(Object target) {
this.target = target;
}
public Object getProxyInstance(){
/*
newProxyInstance方法的参数:
* ClassLoader:类加载器
* 它是用于加载代理对象字节码的。和被代理对象使用相同的类加载器。固定写法。
* Class[]:字节码数组
* 它是用于让代理对象和被代理对象有相同方法。固定写法。
* InvocationHandler:用于提供增强的代码
* 它是让我们写如何代理。我们一般都是些一个该接口的实现类,通常情况下都是匿名内部类,但不是必须的。
* 此接口的实现类都是谁用谁写。
*/
return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object returnValue = null;
System.out.println("增强方法执行..............");
//获取方法的参数
double money = (Double) args[0];
System.out.println("获取到的参数:"+money);
//获取当前方法的名称,可以筛选需要增强的方法
String methodName = method.getName();
System.out.println("获取到的方法名称:"+methodName);
returnValue = method.invoke(target,args);
return returnValue;
}
});
}
}
3.3.3、动态代理之cglib代理
cglib (Code Generation Library )是一个第三方代码生成类库,运行时在内存中动态生成一个子类对象从而实现对目标对象功能的扩展。
cglib特点
- JDK的动态代理有一个限制,就是使用动态代理的对象必须实现一个或多个接口。
如果想代理没有实现接口的类,就可以使用CGLIB实现。 - CGLIB是一个强大的高性能的代码生成包,它可以在运行期扩展Java类与实现Java接口。
它广泛的被许多AOP的框架使用,例如Spring AOP和dynaop,为他们提供方法的interception(拦截)。 - CGLIB包的底层是通过使用一个小而快的字节码处理框架ASM,来转换字节码并生成新的类。
不鼓励直接使用ASM,因为它需要你对JVM内部结构包括class文件的格式和指令集都很熟悉。
cglib与动态代理最大的区别就是
- 使用动态代理的对象必须实现一个或多个接口
- 使用cglib代理的对象则无需实现接口,达到代理类无侵入。
使用cglib需要引入cglib的jar包,如果你已经有spring-core的jar包,则无需引入,因为spring中包含了cglib。
- cglib的Maven坐标
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.2.5</version>
</dependency>
- cglib代理工厂CglibFactory
public class CglibFactory {
private Object target; //这里持有的引用可以是任何对象(除被final修饰的类)
public CglibFactory(Object target) {
this.target = target;
}
public Object getCglibProxy(){
/**
* 参数解释
* Class:字节码
* 用于指定被代理对象字节码
* Callback:用于增强的代码
* 一般写的是该接口的子接口实现类,MethodInterceptor
*/
return Enhancer.create(target.getClass(), new MethodInterceptor() {
/**
* 执行被代理对象的任何方法都会经过该方法
* @param o 代理对象的引用
* @param method 当前执行的方法
* @param objects 当前执行方法的参数
* @param methodProxy 当前执行方法的代理对象
* @return
* @throws Throwable
*/
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
//这里可以写增强的方法
System.out.println("当前执行的方法:"+method.getName());
System.out.println("当前执行方法的参数:"+(Double)objects[0]);
return method.invoke(target,objects);
}
});
}
}
3.4、使用动态代理对案例进行改造
- 用于创建AccountServiceImpl的代理对象的工厂
public class BeanFactory {
private IAccountService accountService;
private TransactionManager txManager;
//省略依赖注入的setter方法
/**
* 获取Service代理对象
* @return
*/
public IAccountService getAccountService() {
return (IAccountService)Proxy.newProxyInstance(accountService.getClass().getClassLoader(),
accountService.getClass().getInterfaces(),
new InvocationHandler() {
/**
* 添加事务的支持
*
* @param proxy
* @param method
* @param args
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object rtValue = null;
try {
//1.开启事务
txManager.beginTransaction();
//2.执行操作
rtValue = method.invoke(accountService, args);
//3.提交事务
txManager.commit();
//4.返回结果
return rtValue;
} catch (Exception e) {
//5.回滚操作
txManager.rollback();
throw new RuntimeException(e);
} finally {
//6.释放连接
txManager.release();
}
}
});
}
}
- 使用xml配置IOC
<!--配置代理的service-->
<bean id="proxyAccountService" factory-bean="beanFactory" factory-method="getAccountService"></bean>
<!--配置beanfactory-->
<bean id="beanFactory" class="com.touwowo.factory.BeanFactory">
<!-- 注入service -->
<property name="accountService" ref="accountService"></property>
<!-- 注入事务管理器 -->
<property name="txManager" ref="txManager"></property>
</bean>
- 使用Junit单元测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:bean.xml")
public class AccountServiceTest {
@Autowired
@Qualifier("proxyAccountService")
private AccountService as;
@Test
public void testTransferAccount(){
as.transferAccount(1,2,100.00);
}
}
3.5、spring中的 AOP
3.5.1、AOP的相关术语
-
Aspect
(切面): Aspect 声明类似于 Java 中的类声明,在 Aspect 中会包含着一些 Pointcut 以及相应的 Advice。 -
Joint point
(连接点):表示在程序中明确定义的点,典型的包括方法调用,对类成员的访问以及异常处理程序块的执行等等,它自身还可以嵌套其它 joint point。 -
Pointcut
(切点):表示一组 joint point,这些 joint point 或是通过逻辑关系组合起来,或是通过通配、正则表达式等方式集中起来,它定义了相应的 Advice 将要发生的地方。 -
Advice
(增强):Advice 定义了在Pointcut
里面定义的程序点具体要做的操作,它通过 before、after 和 around 来区别是在每个 joint point 之前、之后还是代替执行的代码。 -
Target
(目标对象):织入Advice
的目标对象。 -
Weaving
(织入):将Aspect
和其他对象连接起来, 并创建Adviced
object 的过程
其实这些专业术语在学习动态之后就很好理解
3.5.2、AOP入门例子
- 导入AOP需要的依赖,aspectjweaver用于解析AOP切入点表达式。
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.5</version>
</dependency>
- 导入xml约束
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns="http://www.springframework.org/schema/beans"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
</bean>
- 目标:使用AOP完成动态代理实现的功能,实现对功能的增强
- 准备一个通知类,用于增强代码。
public class Logger {
/**
* 用于打印日志,计划让其在切入点 方法执行之前执行(切入点方法就是业务层方法)
* 前置通知
*/
public void beforePrintLog(){
System.out.println("前置通知...........");
}
/**
* 后置通知
*/
public void afterReturnPrintLog(){
System.out.println("后置通知...........");
}
/**
* 异常通知
*/
public void afterThrowingPrintLog(){
System.out.println("异常通知...........");
}
/**
* 最终通知
*/
public void afterPrintLog(){
System.out.println("最终通知...........");
}
}
- 准备待增强的目标对象,即AOP术语中的
target
。
//省略AccountService接口类
public class AccountServiceImpl implements AccountService {
/**
* 模拟保存账户
*/
@Override
public void saveAccount() {
System.out.println("保存账户");
}
}
- 基于xml的AOP配置,包括前置通知,后置通知,异常通知,最终通知。
<!-- spring中基于xml的aop配置
1、把通知Bean也交给spring来管理
2、使用sop:config标签表明开始aop的配置
3、使用aop:aspect标签表明配置切面
id:切面的唯一标识
ref:指定通知类的ID
4、在aop:aspect的内部使用对应的标签来配置通知的类型
method:用于指定Logger类中哪个方法是通知
5、用于指定切入点表达式
关键字:execution(表达式)
表达式:
访问修饰符 返回值类型 包名.包名.包名.类名.方法名(参数列表)
全通配写法
* *..*.*(..)
1、访问修饰符可以省略
2、返回值可以通配符 * 表示任何返回值
3、包名可以用通配符 * 表示, 如果有三级包,就必须写*.*.*.
也可以使用*..表示任意几级包(当前包以及自包)
4、类名和方法均可以使用通配符 *
5、参数列表
基本类型直接写 如int
引用类型 java.lang.String
..表示有无参数均可,也可以使用通配符*(必须有参数)
6、通常写法
* com.touwowo.service.impl.*.*(..)
-->
<!-- 将通知类添加到spring容器中 -->
<bean id="logger" class="com.touwowo.utils.Logger"/>
<!--配置AOP,aop:config表示开始AOP的配置-->
<aop:config>
<!-- 配置切面 -->
<aop:aspect id="logAdvice" ref="logger">
<!--
pointcut-ref用于指定用aop:pointcut已经配置好的切入点表达式,可以减少重复代码
pointcut用于配置切入点表达式
-->
<aop:before method="beforePrintLog"
pointcut-ref="pt1"/>
<aop:after-returning method="afterReturnPrintLog"
pointcut-ref="pt1"/>
<aop:after-throwing method="afterThrowingPrintLog"
pointcut-ref="pt1"/>
<aop:after method="afterPrintLog" pointcut-ref="pt1"/>
<!--
id用于指定表达式的唯一表示 expression用于指定表达式内容
此标签写在aop:aspect标签内部,只能用于当前切面
写在外面,所有切面都能使用
-->
<aop:pointcut id="pt1" expression="execution(* com.touwowo.service.impl.*.saveAccount())"/>
</aop:aspect>
</aop:config>
</beans>
- 使用Junit进行测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:bean.xml"})
public class TestAop {
@Autowired
private AccountService accountService;
@Test
public void test(){
accountService.saveAccount();
}
}
- 输出结果
前置通知...........
保存账户
后置通知...........
最终通知...........
3.5.3、AOP中环绕通知的配置
Spring框架为我们提供了一个接口:ProceedingJoinPoint。该接口有一个方法proceed(),此方法就相当于明确调用切入点方法。
该接口可以作为环绕通知的方法参数,在程序执行时,spring框架会为我们提供该接口的实现类供我们使用。
它是spring框架为我们提供的一种可以在代码中手动控制
增强方法何时执行的方式。
- 在Logger类中添加环绕通知的代码
public Object aroundPrintLog(ProceedingJoinPoint joinPoint){
try {
Object rtValue = null ;
Object[] args = joinPoint.getArgs();
System.out.println("前置通知...........");
rtValue = joinPoint.proceed(args);
System.out.println("后置通知...........");
return rtValue;
} catch (Throwable throwable) {
throwable.printStackTrace();
System.out.println("异常通知...........");
throw new RuntimeException(throwable);
}finally {
System.out.println("最终通知...........");
}
}
其实配置AOP的环绕通知与自己写的动态代理的代码非常相似。
- xml中添加代码
<aop:around method="aroundPrintLog" pointcut-ref="pt1"/>
- 测试结果
前置通知...........
保存账户
后置通知...........
最终通知...........
3.5.4、使用注解开发AOP
- 在xml中添加约束,开启对注解的支持
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 配置spring的 注解支持 -->
<context:component-scan base-package="com.touwowo"/>
<!-- 配置AOP的注解支持 -->
<aop:aspectj-autoproxy/>
</beans>
- 使用注解改造Logger类
package com.touwowo.utils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
@Component("logger")
@Aspect
public class Logger {
/**
* 配置切入点
*/
@Pointcut("execution(* com.touwowo.service.impl.*.*(..))")
public void getPt1(){};
/**
* 用于打印日志,计划让其在切入点 方法执行之前执行(切入点方法就是业务层方法)
* 前置通知
*/
@Before("getPt1()")
public void beforePrintLog(){
System.out.println("前置通知...........");
}
/**
* 后置通知
*/
@AfterReturning("getPt1()")
public void afterReturnPrintLog(){
System.out.println("后置通知...........");
}
/**
* 异常通知
*/
@AfterThrowing("getPt1()")
public void afterThrowingPrintLog(){
System.out.println("异常通知...........");
}
/**
* 最终通知
*/
@After("getPt1()")
public void afterPrintLog(){
System.out.println("最终通知...........");
}
/**
* 环绕通知:spring为我们提供的一种可以在代码中手动控制增强方法何时执行的方式
* 个人觉得:像自己写的动态代理
*/
@Around("getPt1()")
public Object aroundPrintLog(ProceedingJoinPoint joinPoint){
try {
Object rtValue = null ;
Object[] args = joinPoint.getArgs();
System.out.println("前置通知...........");
rtValue = joinPoint.proceed(args);
System.out.println("后置通知...........");
return rtValue;
} catch (Throwable throwable) {
throwable.printStackTrace();
System.out.println("异常通知...........");
throw new RuntimeException(throwable);
}finally {
System.out.println("最终通知...........");
}
}
}
3.5.5、使用AOP完成转账案例的演示
<!-- 其余的配置已省略 -->
<!--配置aop-->
<aop:config>
<!--配置通用切入点表达式-->
<aop:pointcut id="pt1" expression="execution(* com.touwowo.service.impl.*.*(..))"></aop:pointcut>
<aop:aspect id="txAdvice" ref="txManager">
<!--配置前置通知:开启事务-->
<aop:before method="beginTransaction" pointcut-ref="pt1"></aop:before>
<!--配置后置通知:提交事务-->
<aop:after-returning method="commit" pointcut-ref="pt1"></aop:after-returning>
<!--配置异常通知:回滚事务-->
<aop:after-throwing method="rollback" pointcut-ref="pt1"></aop:after-throwing>
<!--配置最终通知:释放连接-->
<aop:after method="release" pointcut-ref="pt1"></aop:after>
</aop:aspect>
</aop:config>
使用注解完成转账案例时应该在TxManaager中配置。
四、spring中的事务控制
4.1、Spring 中事务控制的API介绍
Spring所有事务代理类都是基于PlatformTransactionManager
接口的实现。此接口是spring的事务管理器,它里面提供了我们常用的操作事务的方法,如下代码片段。
-
PlatformTransactionManager
包括以下三个操作:
//获得事务信息
TransactionStatus getTransaction(TransactionDefinition definition)
//提交事务
void commit(TransactionStatus status)
//回滚事务
void rollback(TransactionStatus status)
- 我们在开发中都是使用它的实现类,如下:
//用于Spring JDBC以及Mybatis框架的事务代理
DataSourceTransactionManager
//用于Hibernate框架事务代理
HibernateTransactionManager
//用于Jpa框架的事务代理
JpaTransactionManager
//用于JDO框架的事务代码
JdoTransactionManager
//用于Jta事务代理,一个事务跨多资源必须要使用
JtaTransactionManager
4.2、关于事务的一些概念
4.3、使用spring的事务控制完成转账案例
- 导入依赖
<!-- https://mvnrepository.com/artifact/org.springframework/spring-tx -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.3.3</version>
</dependency>
- 导入xml约束
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
</beans>
- 编写spring配置文件,完成对事务的控制
<!-- spring中基于xml的声明式事务控制的配置 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--配置事务的通知-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<!-- transferAccount为Service中的业务方法 -->
<!--
isolation:指定事务的隔离级别 默认值为default,表示使用数据库的默认级别
propagation:用于指定事务的传播行为,默认值是REQIRED,表示一定会有事务(增删改的选择)
read-only:用于指定事务是否只读,只有查询方法才能设置为true,默认为false
timeout:用于指定事务的超时时间,默认值为-1,表示永不超时,以秒为单位
rollback-for:用于指定一个异常,产生该异常时,事务回滚
no-rollback-for:用于指定一个异常,产生该异常时,事务不回滚,产生其他异常时事务回滚,没有默认值。表示有异常都回滚
-->
<tx:method name="transferAccount" propagation="REQUIRED" read-only="false" />
</tx:attributes>
</tx:advice>
<!-- 配置AOP -->
<aop:config>
<!--配置切入点表达式-->
<aop:pointcut id="pt1" expression="execution( * com.touwowo.service.impl.*.*(..))"/>
<!-- 建立切入点表达式和事务通知的关系 -->
<aop:advisor advice-ref="txAdvice" pointcut-ref="pt1"></aop:advisor>
</aop:config>
- 使用注解时xml的配置
<!-- 开启 spring 对注解事务的支持 -->
<tx:annotation-driven transaction-manager="transactionManager"/>
五、依赖以及配置文件的约束
<dependencies>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.3</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-jdbc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.3.3</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-tx -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.3.3</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-test -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.3.3</version>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.21</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.alibaba/druid -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.3</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.5</version>
</dependency>
<!-- https://mvnrepository.com/artifact/junit/junit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.1</version>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
<scope>provided</scope>
</dependency>
</dependencies>
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
</beans>