本文基于下述教程编写:【B站】ssm教程
在上一天只是实现了AOP
的简单配置,模拟了业务逻辑,并没有真正操作数据库修改数据。也就是没有真正地把AOP
用上场。
Spring的JdbcTemplate基本用法
先来学学简单使用Spring提供的简单数据库操作类:JdbcTemplate
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<!--Spring核心-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<!--Spring jdbcTemplate相关-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<!--Spring 事务相关-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<!--数据库驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
</dependency>
</dependencies>
其中,JdbcTemplate
、DriverManagerDataSource
都是来自spring
的jdbc包下,和JdbcTemplate
一样,DriverManagerDataSource
是Spring提供的一个内置数据源,可以与JdbcTemplate
通过构造函数绑定使用,也可以直接调用setDataSource()
方法。
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
public class jdbcTemplateDemo {
public static void main(String[] args) {
//提供数据源
DriverManagerDataSource driverManagerDataSource=new DriverManagerDataSource();
driverManagerDataSource.setDriverClassName("com.mysql.jdbc.Driver");
driverManagerDataSource.setUrl("jdbc:mysql://localhost:3306/ssmtest");
driverManagerDataSource.setUsername("root");
driverManagerDataSource.setPassword("root");
//创建对象
JdbcTemplate jdbcTemplate=new JdbcTemplate();
jdbcTemplate.setDataSource(driverManagerDataSource);
//执行方法
jdbcTemplate.execute("insert into spring_account(username,money) values('张三',100)");
}
}
结合AOP思想,可将JdbcTemplate
、DriverManagerDataSource
对象放进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">
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"/>
</bean>
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/ssmTest"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</bean>
</beans>
测试代码即可简写成:
public static void main(String[] args) {
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
JdbcTemplate jdbcTemplate=ac.getBean("jdbcTemplate",JdbcTemplate.class);
//自定义RowMapper形式
List<Account> accounts=jdbcTemplate.query("select * from spring_account where money<?",new AccountRowMapper(),10000f);
//使用Spring官方RowMapper形式
List<Account> accounts=jdbcTemplate.query("select * from spring_account where MONEY<?",
new BeanPropertyRowMapper<Account>(Account.class), 10000f);
//查询操作
//jdbcTemplate.execute("select * from spring_account");
//保存操作
//jdbcTemplate.update("insert into spring_account(USERNAME,money)values(?,?)","ABC",5555f);
//更新操作
//jdbcTemplate.update("update spring_account set username=?,money=? where id=?","李四",6666,7);
//删除
//jdbcTemplate.update("delete from spring_account where id=?",7);
}
class AccountRowMapper implements RowMapper{
@Override
public Object mapRow(ResultSet rs, int i) throws SQLException {
Account account=new Account();
account.setId(rs.getInt("id"));
account.setName(rs.getString("username"));
account.setMoney(rs.getDouble("money"));
return account;
}
可以留意到,JdbcTemplate
是通过update()
方法实现增、删、改三大功能的,同时execute()
也是可以直接执行这三块操作。他们的参数都是带占位符的SQL语句以及可变参数组成。
最为复杂的是查询方法query()
,除了带占位符的SQL、可变参数,中间还需要一个RowMapper
对象,其实RowMapper
只是一个接口,要么自定义一个实现它的实现类,要么使用Spring为我们提供的BeanPropertyRowMapper
作为参数传入。两种用意都是帮我们封装查询结果信息到实体类对象。主要区别在于,自定义RowMapper
实现类要写好具体封装逻辑,而BeanPropertyRowMapper
不用,只需要给他对应的实体类class文件即可自动完成封装。
不知道是不是版本问题,BeanPropertyRowMapper
封装时抽风,偶尔的字段信息封装失败为NULL
。改数据库字段实体类成员属性都没办法解决,但最后又莫名正常了。
抽取Dao中变量声明及set方法
有一个显而易见的问题,如果有很多其他实体类的Dao
操作类,上面框框中的代码岂不是都要重复挂一遍?
为减少代码冗余,Spring为我们提供了一个叫JdbcDaoSupport
父类让我们继承,即可不用重复上述代码。
org.springframework.jdbc.core.support.JdbcDaoSupport;
该类内部封装了一个JdbcTemplate
,当Spring通过配置文件实例化DriverManagerDataSource
时会自动完成JdbcTemplate
对象的初始化,那样关于JdbcTemplate
对象的初始化以及其set方法
甚至DriverManagerDataSource
的set方法
都不需要写,Spring自动为我们写好了。只要继承该类,直接在xml配置文件中指定DriverManagerDataSource
即可。
但是如果通过注解配置直接继承该类是无法完成标注自动注入变量的,想简洁代码,只有靠自己写中间类实现与JdbcDaoSupport
类似逻辑。
思考了一下,虽然AccountDao
继承的父类JdbcDaoSupport
中只是声明了JdbcTemplate
,但是也存在setDataSource()
方法:
通过上面注入,AccountDao
一样可以用上Spring容器中的DataSource
,并由此初始化带有数据源的JdbcTemplate
,本质上来讲,SpringIOC的XML配置标签<property>
就是调用对应的set方法,将对应的Bean作为参数引入方法,该类有没有具体声明set方法的bean成员变量无所谓,spring不会去检查。此种写法本意上就是拿到参数调用方法。
总结:只需要配置AccountDao
中的DataSource
即可完成JdbcTemplate
的初始化。
IOC、AOP的CRUD应用
由于实现的事务控制需要通过Connection
的rollback()
方法的途径回滚事务,所以还是要用回Dbutils
和c3p0
操作数据库。模拟一个转账操作。大致流程如下:
表现层Service
操作类:
@Override
public void transfer(String sourceName, String targetName, Float money) {
Account source=accountDao.findAccountByName(sourceName);
Account target=accountDao.findAccountByName(targetName);
source.setMoney(source.getMoney()-money);
target.setMoney(target.getMoney()+money);
accountDao.updateAccount(source);
int i=1/0;
accountDao.updateAccount(target);
}
持久化层Dao
操作类:
public class AccountDaoimpl implements IAccountDao {
//JdbcTemplate jdbcTemplate=null;
/*public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}*/
ConnectionUtils connectionUtils = null;
QueryRunner queryRunner = null;
public void setQueryRunner(QueryRunner queryRunner) {
this.queryRunner = queryRunner;
}
public void setConnectionUtils(ConnectionUtils connectionUtils) {
this.connectionUtils = connectionUtils;
}
@Override
public void updateAccount(Account account) {
try {
queryRunner.update(connectionUtils.getThreadConnection(),
"update spring_account set username =?,money= ? where id=? ",
account.getUsername(), account.getMoney(), account.getId());
} catch (SQLException throwables) {
throwables.printStackTrace();
}
//jdbcTemplate.update("update spring_account set username =?,money= ? where id=? ",account.getUsername(),account.getMoney(),account.getId());
}
@Override
public Account findAccountByName(String a) {
List<Account> accounts = null;
try {
accounts = queryRunner.query(connectionUtils.getThreadConnection(), "select * from spring_account where username =?",
new BeanListHandler<Account>(Account.class), a);
} catch (SQLException throwables) {
throwables.printStackTrace();
}
//List<Account> accounts=jdbcTemplate.query("select * from spring_account where username =?",new BeanPropertyRowMapper<Account>(Account.class),a);
//查询username不可能返回多个结果,只能返回一个并且是第一个
if (accounts.size() > 1) {
System.out.println("结果集不唯一");
return null;
}
return accounts.get(0);
}
}
QueryRunner
和JdbcTemplate
使用上有明显区别:前者无论更新、查询操作需要一个提供一个Connection
,后者不需要。相同点也有,在查询方法上,前者需要一个 ResultSetHandler
实现类,后者需要一个RowMapper
实现类。这两个接口的实现类都是将查询结果封装到实体类List
中,也都需要传入实体类Class
文件。
事务管理类TransactionManager
,即切面类:
public class TransactionManager {
private ConnectionUtils connectionUtils;
public void setConnectionUtils(ConnectionUtils connectionUtils) {
this.connectionUtils = connectionUtils;
}
/**
* 开启事务
*/
public void beginTransaction(){
try {
System.out.println("beginTransaction()启动");
connectionUtils.getThreadConnection().setAutoCommit(false);
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
/**
* 提交事务
*/
public void commit(){
try {
System.out.println("commit()启动");
connectionUtils.getThreadConnection().commit();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
/**
* 回滚事务
*/
public void rollback(){
try {
System.out.println("rollback()启动");
connectionUtils.getThreadConnection().rollback();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
/**
* 释放连接
*/
public void release(){
try {
System.out.println("release()启动");
//还回连接池中
connectionUtils.getThreadConnection().close();
/**
* 服务器提供一个线程,该线程拥有一个Connection,当线程关闭Connection时
* Connection就不能用了,线程结束会还回服务器线程池,但是此时还回去的线程
* 还拥有一个已经关闭了的Connection,再次调用该线程获取连接时,一定存在Connection
* 但已经关闭还回连接池不能再使用,所以需要线程和连接解绑
*/
connectionUtils.remove();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
public Object aroundPrintLog(ProceedingJoinPoint proceedingJoinPoint) {
Object rtValue = null;
try {
Object[] args=proceedingJoinPoint.getArgs();
System.out.println("执行了前置通知");
beginTransaction();
rtValue=proceedingJoinPoint.proceed(args);
//下述为Proxy动态代理实现的代码,注意区别
//rtValue=method.invoke(accountService,args);
System.out.println("执行了后置通知");
commit();
return rtValue;
} catch (Throwable e) {
System.out.println("执行了异常通知");
rollback();
throw new RuntimeException();
} finally {
System.out.println("执行了最终通知");
release();
}
}
}
连接管理类ConnectionUtils
,连接工具类,它用于从数据源获取一个连接,并且实现和线程绑定:
public class ConnectionUtils {
private ThreadLocal<Connection> threadLocal = new ThreadLocal<>();
private DataSource dataSource;
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
/**
* 获取当前线程上的连接
*/
public Connection getThreadConnection() {
try {
//1.先从ThreadLocal上获取
Connection conn = threadLocal.get();
//2.判断线程上有无连接
if (conn == null) {
//3.从数据源中获取一个连接,并且存入ThreadLocal中
conn = dataSource.getConnection();
threadLocal.set(conn);
}
return conn;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public void remove(){
threadLocal.remove();
}
}
最重要一环,bean.xml
配置IOC、AOP切面:
<?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"
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 id="accountService" class="com.spring_aop2.service.impl.AccountServiceImpl">
<property name="accountDao" ref="accountDao"/>
</bean>
<bean id="accountDao" class="com.spring_aop2.dao.impl.AccountDaoimpl">
<property name="connectionUtils" ref="connectionUtils"/>
<property name="queryRunner" ref="queryRunner"/>
</bean>
<bean id="queryRunner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype"/>
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/ssmTest"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</bean>
<!--连接工具类-->
<bean id="connectionUtils" class="com.spring_aop2.utils.ConnectionUtils">
<property name="dataSource" ref="dataSource"/>
</bean>
<bean id="transactionManager" class="com.spring_aop2.utils.TransactionManager">
<property name="connectionUtils" ref="connectionUtils"/>
</bean>
<!--Spring AOP配置-->
<aop:config>
<!--配置切面-->
<aop:aspect id="transactionManagerAdvice" ref="transactionManager">
<!--配置通知类型,并且关联被代理增强类方法-->
<!--前置通知,被增强方法执行前执行-->
<!--<aop:before method="beginTransaction" pointcut-ref="p"/>
<aop:after-returning method="commit" pointcut-ref="p"/>
<aop:after-throwing method="rollback" pointcut-ref="p"/>
<aop:after method="release" pointcut-ref="p"/>-->
<aop:around method="aroundPrintLog" pointcut-ref="p"/>
<!--自定义切入点表达式-->
<aop:pointcut id="p" expression="execution( * com.spring_aop2.service.impl.AccountServiceImpl.*(..))"/>
</aop:aspect>
</aop:config>
</beans>
经测试:
事务经由AOP完全控制住了。
springJDBC一体化运用——声明式事务
以上我们是使用DButils
的QueryRunner
(Dao操作数据库类)、springJdbc的DriverManagerDataSource
(DataSource的实现类,提供Connection
)、TransactionManager
(自己写的事务管理器切面类)实现的事务控制。增强代码主要是控制线程唯一变量ThreadLocal<Connection>
中的Connection
的各种事务控制方法实现取消自动提交
、手动提交
、异常回滚事务
、释放Connection
、和线程解绑
功能。
而spring框架一一为我们提供了Dao操作数据库类——JdbcTemplate
、事务管理器切面类——DataSourceTransactionManager
,加上之前用过的DriverManagerDataSource
,就可以全靠spring容器完成事务控制了。
声明式事务XML配置
由于DriverManagerDataSource
、JdbcTemplate
都是springJDBC的内容,必须引入spring-jdbc
这个依赖。
另外使用声明式事务,意味着事务管理全交给Spring完成,需要引入spring-tx
内容。使用Spring内置的事务管理器——DataSourceTransactionManager
:
<!--spring核心容器-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<!--spring声明式事务支持-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<!--Spring jdbcTemplate、DriverManagerDataSource相关-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<!--数据库连接驱动包-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
</dependency>
<!--切片表达式支持-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.7</version>
<scope>compile</scope>
</dependency>
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: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/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">
<bean id="accountService" class="com.spring_transaction.service.impl.AccountServiceImpl">
<property name="accountDao" ref="accountDao"/>
</bean>
<!--绑定JdbcTemplate的数据源-->
<bean id="accountDao" class="com.spring_transaction.dao.impl.AccountDaoImpl">
<property name="dataSource" ref="dataSource"/>
</bean>
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/ssmTest"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</bean>
<!--配置事务管理器,包含对Dao操作的各种增强方法-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--配置事务的通知-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<!--配置事务的属性-->
<tx:attributes>
<tx:method name="*" propagation="REQUIRED" read-only="false"/>
<tx:method name="find*" propagation="SUPPORTS" read-only="true"/>
</tx:attributes>
</tx:advice>
<aop:config>
<!--通用化切入点表达式配置-->
<aop:pointcut id="pt1" expression="execution(* com.spring_transaction.service.impl.AccountServiceImpl.*(..))"/>
<!--建立对应关系-->
<aop:advisor advice-ref="txAdvice" pointcut-ref="pt1"/>
</aop:config>
</beans>
在<tx:attributes>
标签里有一些属性注意含义:
read-only:是否是只读事务。默认 false,不只读。
isolation:指定事务的隔离级别。默认值是使用数据库的默认隔离级别。
propagation:指定事务的传播行为。
timeout:指定超时时间。默认值为:-1。永不超时。
rollback-for:用于指定一个异常,当执行产生该异常时,事务回滚。产生其他异常,事务不回滚。 没有默认值,任何异常都回滚。
no-rollback-for:用于指定一个异常,当产生该异常时,事务不回滚,产生其他异常时,事务回 滚。没有默认值,任何异常都回滚。
约束主要多了新的这三行,IOC功能的以bean
结尾,AOP的功能以aop
结尾,声明式事务功能以tx
结尾:
xmlns:tx="http://www.springframework.org/schema/tx"
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
上面我们让Dao操作类继承自JdbcDaoSupport
,实现抽取Dao中变量声明及set方法,照葫芦画瓢,我们也这样设置。温习一下,这里面什么成员变量以及其set方法
都没写,即便这样也可以实现数据库操作,只需要调用父类JdbcDaoSupport
的getJdbcTemplate()
方法即可获得。至于数据源怎么设置到JdbcTemplate
的,主要是JdbcDaoSupport
里面有一个setDataSource()
方法,在xml文件里配置了一个DataSource
成员变量(尽管并没有声明),setDataSource()
方法触发,利用传入的DriverManagerDataSource
bean对象赋值给DataSource
形参,借此bean实例化一个拥有同样数据源的JdbcTemplate
对象。
AccountDao
实现类:
public class AccountDaoImpl extends JdbcDaoSupport implements IAccountDao {
//更新账户
@Override
public void updateAccount(Account account) {
try {
getJdbcTemplate().update("update spring_account set username =?,money= ? where id=? ",
account.getUsername(),account.getMoney(),account.getId());
} catch (Exception e) {
throw new RuntimeException(e);
}
}
//根据账户用户名返回账户
@Override
public Account findAccountByName(String name) {
try {
/*List<Account> accounts = queryRunner.query(connectionUtils.getThreadConnection(), "select * from spring_account where username= ?", name,
new BeanListHandler<Account>(Account.class));*/
List<Account> accounts =getJdbcTemplate().query("select * from spring_account where username= ?", new BeanPropertyRowMapper<Account>(Account.class), name);
if (accounts == null || accounts.size() == 0) {
throw new RuntimeException("查找账户结果集为空,数据有问题");
}
if (accounts.size() > 1) {
throw new RuntimeException("查找账户结果集不唯一,数据有问题");
}
return accounts.get(0);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
总体上,声明式事务配置步骤划分以下三大步:
第一步:配置事务管理器:
对于事务管理器不再需要我们自定义,只需要引用DataSourceTransactionManager
,传入一个数据源对象作为成员变量,这里我们传入IOC的bean——DriverManagerDataSource
类对象。
第二步:配置事务的通知:
通知是切面的切入点表达式的要增强的目标方法所指定代码逻辑,通知可以在目标方法的不同位置出现。而这些方法在不同的通知里面可以独立设置属性。
在这里我们指定该事务管理器控制哪些方法的事务操作。
属性设置:
含修改属性的更新方法:propagation="REQUIRED" read-only="false"
不含修改属性的查询方法: propagation="SUPPORTS" read-only="true"
第三步:配置AOP(切入点表达式、指定其增强的方法)
经过这样的配置,事务控制就不需要开发人员操心了。
声明式事务注解配置
回顾一下声明式事务配置:
纯xml配置:
半注解配置:
以上只针对配置文件、dao
以及service
修改。相比之下,半注解方式除了减少了对AccountDao
、AccountService
的注明,同时还用了一行:
<!--开启Spring对声明式事务支持-->
<tx:annotation-driven transaction-manager="transactionManager"/>
直接减少了以下在XML中前者需要的配置,其实具体信息标志全都挪到了AccountService
里面的注解,声明式事务注解是对服务层的方法进行修饰。
@Transactional(propagation = Propagation.REQUIRED,readOnly = false)
该注解的属性和 xml 中的属性含义一致。该注解可以出现在接口上,类上和方法上。 出现接口上,表示该接口的所有实现类都有事务支持。 出现在类上,表示类中所有方法有事务支持。出现在方法上,表示方法有事务支持。 以上三个位置的优先级:方法>类>接口。
对应XML中<tx:attributes>
标签的各种用来表明读写性和增强哪些方法的各种属性。
<!--配置事务的通知-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<!--配置事务的属性-->
<tx:attributes>
<tx:method name="*" propagation="REQUIRED" read-only="false"/>
<tx:method name="find*" propagation="SUPPORTS" read-only="true"/>
</tx:attributes>
</tx:advice>
<aop:config>
<!--通用化切入点表达式配置-->
<aop:pointcut id="pt1" expression="execution(* com.spring_transaction.service.impl.AccountServiceImpl.*(..))"/>
<!--建立对应关系-->
<aop:advisor advice-ref="txAdvice" pointcut-ref="pt1"/>
</aop:config>
但是对事务管理器的声明两者都在xml写明:
<!--配置事务管理器,包含对Dao操作的各种增强方法-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
完全摆脱xml的约束,该怎么做?纯注解实现声明式事务支持?
首先明确,全注解实现要做到全部替代半注解XML文件里面的内容。此时不再依赖bean.xml情况下,实现以下几步:
XML配置的基础上准备多几个类JdbcConfig
数据库连接、SpringConfig
注解配置类(这对去掉xml文件注解实现必不可少)、TransactionConfig
事务配置类:
需要用到的依赖:
<!--替换运行器需要用到@RunWith注解-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>compile</scope>
</dependency>
<!--单元测试@test注解-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<!--Spring提供Junit运行器所在的包-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<!--spring核心容器-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<!--spring声明式事务支持-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<!--Spring jdbcTemplate相关-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<!--数据库连接驱动包-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
</dependency>
注解配置类SpringConfig
:
@ComponentScan("com.spring_transactionAnnotationPure")
@Configuration
@Import({JdbcConfig.class,TransactionConfig.class})
@PropertySource("jdbcConfig.properties")
@EnableTransactionManagement//开启事务注解支持
public class SpringConfig {
}
在这里扫描bean类所在的包
、注明该类是注解配置类
、引入数据库连接类以及声明式事务配置类
、引入数据库连接的资源文件
、开启声明式事务注解支持
,五个操作都在这里实现了。
数据库连接类JdbcConfig
:
public class JdbcConfig {
@Value("${jdbc.username}")
public String username;
@Value("${jdbc.password}")
public String password;
@Value("${jdbc.url}")
public String url;
@Value("${jdbc.driver}")
public String driver;
@Bean(name = "jdbcTemplate")
public JdbcTemplate createJdbcTemplate(DataSource dataSource){
return new JdbcTemplate(dataSource);
}
@Bean(name = "dataSource")
public DataSource createDtaaSource(){
DriverManagerDataSource driverManagerDataSource=new DriverManagerDataSource();
driverManagerDataSource.setUsername(username);
driverManagerDataSource.setPassword(password);
driverManagerDataSource.setUrl(url);
driverManagerDataSource.setDriverClassName(driver);
return driverManagerDataSource;
}
}
主要对资源文件jdbcConfig.properties
进行读取并赋值给bean生成数据源对象(值得注意,该数据源是SpringJdbc
的DriverManagerDataSource
),并且完成对JdbcTemplate
的bean标注。
声明式事务配置类TransactionConfig
:
public class TransactionConfig {
@Bean(name = "transactionManager")
public PlatformTransactionManager createTransactionManager(DataSource dataSource){
return new DataSourceTransactionManager(dataSource);
}
}
该类只是标注了返回的是PlatformTransactionManager
接口对象,实际上是实例化返回DataSourceTransactionManager
对象,该对象是直接继承自AbstractPlatformTransactionManager
,而AbstractPlatformTransactionManager
又实现了PlatformTransactionManager
接口。
我们可以发现,由于setDataSource()
方法的存在,在DataSourceTransactionManager
里面可以直接使用数据源对象DataSource
AccountDao
以及AccountService
和半注解方式无异,内容保持一致。结合Spring
集成Junit
方式,更换Junit运行器、配置类引入、自动注入IAccountService
、标记单元测试方法:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig.class)
public class testTransactionAOP {
/*ApplicationContext ac= new ClassPathXmlApplicationContext("bean.xml");
IAccountService accountService= (IAccountService) ac.getBean("accountService");
accountService.transfer("大哥大","大姐",100f);*/
@Autowired
private IAccountService accountService;
@Test
public void testTransfer(){
accountService.transfer("大哥大","大姐",100f);
}
}
经测试,事务被控制住了。总结一下从半注解到全注解我们干了哪些事:
1.将声明式事务管理器写成配置类,挪到了TransactionConfig
,分离开来注明bean。
2.将JdbcTemplate、DataSource挪到了JdbcConfig
注明bean。
3.扫描要bean所在的包也在SpringConfig
注明
4.开启声明式事务支持。
就使用的便携性来看,实际是注解和XML差别主要在于半注解所带来的XML配置文件的简便。XML多了配置事务通知、配置切面这两个步骤,而半注解XML中只需要开启声明式事务的支持就可以了,代价就是需要在服务层每个方法里面注明该方法适用于哪种类型的事务控制。各有优劣。
JDK版本性能测试
public class testJDKEffect {
static int count=1000*1000*1000;
public static void main(String[] args) throws InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
System.out.println("当前JDK版本"+System.getProperty("java.version"));
test();
test2();
test3();
test4();
}
private static void test4() throws IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
long begin=System.currentTimeMillis();
Class<Person> c=Person.class;
Person person=c.newInstance();
Method method=c.getMethod("setAge", Integer.class);
for (int i=0;i<count;i++){
method.invoke(person,33);
person.setAge(2);
}
long end=System.currentTimeMillis();
System.out.println("反射创建对象只赋初值方法耗时:"+(end-begin));
}
private static void test3() throws IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
long begin=System.currentTimeMillis();
for (int i=0;i<count;i++){
Class<Person> c=Person.class;
Person person=c.newInstance();
Method method=c.getMethod("setAge", Integer.class);
method.invoke(person,33);
person.setAge(2);
}
long end=System.currentTimeMillis();
System.out.println("反射创建对象并赋初值方法耗时:"+(end-begin));
}
private static void test2() {
long begin=System.currentTimeMillis();
Person person=new Person();
for (int i=0;i<count;i++){
person.setAge(2);
}
long end=System.currentTimeMillis();
System.out.println("循环10亿只赋值方法耗时:"+(end-begin));
}
private static void test() {
long begin=System.currentTimeMillis();
for (int i=0;i<count;i++){
Person person=new Person();
person.setAge(2);
}
long end=System.currentTimeMillis();
System.out.println("循环10亿对实例化对象并赋值方法耗时:"+(end-begin));
}
}
System.currentTimeMillis()
获得的是自1970-1-01 00:00:00.000 到当前时刻的时间距离,类型为long
Class
类传入泛型并以实体类实例化可以构造反射对象,再使用newInstance()
方法就可以反射实例化对象。
同时,该对象使用getMethon()
方法传入参数实体类set方法名及其参数对应的字节码文件,可以得到Methon
对象。而Methon
对象可以使用invoke()
方法传入实体类对象和需要初始化的参数从而完成给对象赋值的操作。
JDK1.8的成绩:
JDK1.7的成绩: