这里写目录标题
以xml配置形式
-
<dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.1.5.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>5.0.6.RELEASE</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.6</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-tx</artifactId> <version>5.0.6.RELEASE</version> </dependency> </dependencies>
-
目录结构
-
业务逻辑代码(以 xml 配置形式注入)
service 调用 DAO层代码,dao 操作数据库。。。此处仅展示 dao 实现层代码public class AccountDaoImpl implements AccountDao { private JdbcTemplate jdbcTemplate; public void setJdbcTemplate(JdbcTemplate jdbcTemplate) { this.jdbcTemplate = jdbcTemplate; } /** * 要么没有,要么只有唯一,根据Id查询 **/ @Override public Account findAccountById(int id) { List<Account> accountList = jdbcTemplate.query("select * from account where id=?", new BeanPropertyRowMapper<>(Account.class), id); return accountList.isEmpty()?null:accountList.get(0); } @Override public List<Account> findAllCount() { List<Account> list = jdbcTemplate.query("select * from account", new BeanPropertyRowMapper<>(Account.class)); return list.isEmpty()?null:list; } @Override public Account findByName(Account account) { List<Account> accounts = jdbcTemplate.query("select * from account where name=?", new BeanPropertyRowMapper<>(Account.class), account.getName()); if (accounts.isEmpty()) return null; if (accounts.size() > 0) throw new RuntimeException("结果集不唯一..."); return accounts.get(0); } }
-
spring 配置文件
<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 id="accountDaoImpl" class="com.lhg.spring.dao.impl.AccountDaoImpl"> <property name="jdbcTemplate" ref="jdbcTemplate" /> </bean> <bean id="accountServiceImpl" class="com.lhg.spring.service.impl.AccountServiceImpl"> <property name="accountDao" ref="accountDaoImpl"/> </bean> <!--配置数据源--> <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://192.168.25.132:3306/ssm" /> <property name="username" value="root"/> <property name="password" value="root"/> </bean> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <property name="dataSource" ref="dataSource" /> </bean> </beans>
-
测试代码
public class Test { public static void main(String[] args) { ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml"); AccountService accountService = (AccountService) ac.getBean("accountServiceImpl"); List<Account> allCount = accountService.findAllCount(); allCount.forEach(account -> System.out.println(account)); } }
以纯注解形式
-
pom.xml ,新增测试 jar 包
<dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.1.5.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>5.0.6.RELEASE</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.6</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-tx</artifactId> <version>5.0.6.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>5.0.6.RELEASE</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> </dependency> </dependencies>
-
业务层数据访问层全部使用注解形式,并去掉 set 方法,其它代码不变
-
新建一个叫 config 的包,此包下新建 SpringConfig和JdbcConfig文件,分别用来代替 applicationContext.xml 和 JdbcTemplate 配置数据源部分
/** * spring的配置类,相当于applicationContext.xml */ @Configuration @ComponentScan("com.lhg.spring.*") //扫描包 @Import(JdbcConfig.class)//引入其它的xml配置 @PropertySource("db.properties")//读取数据库配置 properties文件注解 public class SpringConfig { }
/** * 和连接数据库相关的配置类 */ public class JdbcConfig { @Value("${jdbc.driver}") private String driver; @Value("${jdbc.url}") private String url; @Value("${jdbc.username}") private String username; @Value("${jdbc.password}") private String password; /** * 创建jdbctemplate对象,但此时没有进容器,要想进容器,需要一个bean注解 */ @Bean(name="jdbcTemplate")//是name不是value public JdbcTemplate createJdbcTemplate(DataSource dataSource){ return new JdbcTemplate(dataSource); } @Bean(name="dataSource") public DataSource createDataSource(){ DriverManagerDataSource dataSource = new DriverManagerDataSource(); dataSource.setDriverClassName(driver); dataSource.setUrl(url); dataSource.setUsername(username); dataSource.setPassword(password); return dataSource; } }
-
test 注解测试
/** * 使用 Junit 单元测试,测试我们的配置 * Srping 整合 junit 的配置 * 1、导入 jar 包 * 2、使用 Junit 提供的注解把 main 方法替换了,替换成 spring 提供的 @RunWith * 3、告知 spring 运行器,spring 和 IOC 创建时基于 xml 还是 注解,并且说明位置 * @ContextConfiguration * locations:指定 xml 文件的位置,加上 classpath关键字表示在类路径下 * classes:指定注解类所在位置 */ @RunWith(SpringJUnit4ClassRunner.class)//使得Test.java拥有IOC环境 @ContextConfiguration(classes = SpringConfig.class) public class Test { @Autowired private AccountService accountService; @org.junit.Test public void test(){ List<Account> allCount = accountService.findAllCount(); allCount.forEach(account -> System.out.println("纯注解:"+account)); } }
spring AOP(基于 xml 配置)
什么是 AOP:在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
说人话:简单的说就是把程序重复的代码抽取出来,在需要执行的时候,使用动态代理技术,在不修改源码的基础上对原有方法进行增强
优势:减少重复代码、提高开发效率、维护方便
-
AOP 实现步骤
-
导入 jar 包
<dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.1.5.RELEASE</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.8.13</version> </dependency> </dependencies>
-
工程结构
通用的 MVC 三层架构工程,为了简单,此工程并为用到 DAO 层代码,即没有查数据库 -
需要增强的业务方法
public class AccountServiceImpl implements AccountService { //对accountSave方法aop @Override public void accountSave() { System.out.println("accoutSave..."); int i = 1 / 0;//有异常 } }
-
利用 LoggerUtil对原有方法增强
public class LoggerUtil { public void beforeAdvice(){ System.out.println("前置通知。。。开始打印日志...."); } public void afterAdvice(){ System.out.println("后置通知。。。打印完了日志...."); } public void throwingrintAdvice(){ System.out.println("异常通知。。打印了异常通知...."); } public void finalAdvice(){ System.out.println("最终通知打印了异常通知...."); } }
-
applicationContext.xml (配置前置、后置、异常、最终四个通知)
<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.lhg.spring.service.impl.AccountServiceImpl" /> <bean id="logger" class="com.lhg.spring.utils.LoggerUtil" /> <aop:config> <!--配置切入点表达式,简化通知配置 如果不写aop:pointcut 切入点表达式标签,在每个通知标签都要写 pointcut 属性,繁琐 Id属性用于指定表达式的唯一标识,expression用于指定表达式内容 此标签写在aop:aspect>标签内部只能当前切面使用 还可以写在aop:aspect>外面,此时就变成所有切面可用 --> <aop:pointcut id="mypoint" expression="execution(* com.lhg.spring.service.impl.*.*())"/> <aop:aspect id="logAdvice" ref="logger"> <aop:before method="beforeAdvice" pointcut-ref="mypoint"/> <!--后置通知--> <aop:after-returning method="afterAdvice" pointcut-ref="mypoint"/> <!--异常通知--> <aop:after-throwing method="throwingrintAdvice" pointcut-ref="mypoint" /> <!--最终通知--> <aop:after method="finalAdvice" pointcut-ref="mypoint"/> </aop:aspect> </aop:config> </beans>
-
test 进行测试
public class AOPTest { public static void main(String[] args) { ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml"); AccountService accountService = (AccountService) ac.getBean("accountServiceImpl"); accountService.accountSave(); } }
效果如下
-
测试 spring 环绕通知
-
在 LoggerUtil 新增环绕通知方法
/** * spring 框架为我们提供了一个接口:ProceedingJoinPoint,该接口有一个方法proceed(), * 此方法就相当于明确调用切入点方法,该接口可以作为环绕通知的方法参数,在程序执行时, * spring 框架会提供该接口的实现类供我们使用 **/ public Object aroundPringLog(ProceedingJoinPoint pjp){ try { Object res ; Object[] args = pjp.getArgs();//这个参数就是实际方法执行所需的参数 System.out.println("写在proceed之前是前置通知--->环绕..前置"); res = pjp.proceed(args);//明确调用业务层方法(切入点方法) System.out.println("写在proceed之后是后置通知--->环绕..后置"); return res; }catch (Throwable t){//这个异常必须写Throwable,Exception拦不住的 System.out.println("写在proceed之后catch里面--->环绕..异常"); throw new RuntimeException(t); } finally { System.out.println("写在proceed之后finally里面--->环绕..最终"); } }
-
applicationContext.xml 新增环绕通知 AOP 配置
<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.lhg.spring.service.impl.AccountServiceImpl" /> <bean id="logger" class="com.lhg.spring.utils.LoggerUtil" /> <aop:config> <!--配置切入点表达式,简化通知配置 Id属性用于指定表达式的唯一标识,expression用于指定表达式内容 此标签写在aop:aspect>标签内部只能当前切面使用 还可以写在aop:aspect>外面,此时就变成所有切面可用 --> <aop:pointcut id="mypoint" expression="execution(* com.lhg.spring.service.impl.*.*())"/> <aop:aspect id="logAdvice" ref="logger"> <!--配置环绕通知--> <aop:around method="aroundPringLog" pointcut-ref="mypoint" /> </aop:aspect> </aop:config> </beans>
-
测试效果
-
spring AOP(基于 注解配置)
-
service 层改动
@Service public class AccountServiceImpl implements AccountService { //对accountSave方法aop @Override public void accountSave() { System.out.println("accoutSave..."); int i = 1 / 0; } }
-
LogggerUtils 改动(注意通知括号内必须带上"pointCut()")
@Component @Aspect//表示当前类是一个切面 public class LoggerUtil { //指定切入点表达式 @Pointcut("execution(* com.lhg.spring.service.impl.*.*())") public void pointCut(){} @Before("pointCut()") public void beforeAdvice(){ System.out.println("前置通知。。。开始打印日志...."); } @AfterReturning("pointCut()") public void afterAdvice(){ System.out.println("后置通知。。。打印完了日志...."); } @AfterThrowing("pointCut()") public void throwingrintAdvice(){ System.out.println("异常通知。。打印了异常通知...."); } @After("pointCut()") public void finalAdvice(){ System.out.println("最终通知打印了异常通知...."); } }
-
applicationContext.xml 改动(注意新增context约束)
<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.lhg.spring" /> <!--配置 spring 开启AOP 注解支持--> <aop:aspectj-autoproxy /> </beans>
-
测试代码及效果
public class AOPTest { public static void main(String[] args) { ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml"); AccountService accountService = (AccountService) ac.getBean("accountServiceImpl"); accountService.accountSave(); } }
可以看到异常通知和最终通知输出结果并没有按预期那样的顺序,这个。。。是改变不了的。spring基于注解的 AOP 这四种通知确实有这种顺序的问题,所以在实际开发中要有个慎重的考虑,但是如果是基于注解的环绕通知是不会有这种顺序问题的,如下:
@Component @Aspect//表示当前类是一个切面 public class LoggerUtil { //指定切入点表达式 @Pointcut("execution(* com.lhg.spring.service.impl.*.*())") public void pointCut(){} /** * spring 框架为我们提供了一个接口:ProceedingJoinPoint,该接口有一个方法proceed(), * 此方法就相当于明确调用切入点方法,该接口可以作为环绕通知的方法参数,在程序执行时, * spring 框架会提供该接口的实现类供我们使用 **/ @Around("pointCut()") public Object aroundPringLog(ProceedingJoinPoint pjp){ try { Object res ; Object[] args = pjp.getArgs();//这个参数就是实际方法执行所需的参数 System.out.println("写在proceed之前是前置通知--->环绕..前置"); res = pjp.proceed(args);//明确调用业务层方法(切入点方法) System.out.println("写在proceed之后是后置通知--->环绕..后置"); return res; }catch (Throwable t){//这个异常必须写Throwable,Exception拦不住的 System.out.println("写在proceed之后catch里面--->环绕..异常"); throw new RuntimeException(t); } finally { System.out.println("写在proceed之后finally里面--->环绕..最终"); } } }
测试效果如下
一个更加好理解的图示
实战–基于 xml 的AOP 实现事务控制(spring内置声明式事务)
转账问题:如下,每次操作数据库时都获取一个连接,当出现异常时,前面正确执行的成功提交了,而异常后面的语句则没有提交,导致一方账户钱减少而另一方账户钱并没有增加。
解决办法:应该共用一个连接,用 ThreadLocal 把 Connection 和当前线程绑定,从而使一个线程中只有一个能控制事务的对象,事务控制应该都是放在业务层
-
依赖 jar 包
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.lhg</groupId> <artifactId>spring_day03</artifactId> <version>1.0-SNAPSHOT</version> <packaging>jar</packaging> <dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.1.5.RELEASE</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.8.13</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>5.0.6.RELEASE</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.6</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-tx</artifactId> <version>5.0.6.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>5.0.6.RELEASE</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> </dependency> </dependencies> </project>
-
AccountDaoImpl 代码
public class AccountDaoImpl implements AccountDao { private JdbcTemplate jdbcTemplate; public void setJdbcTemplate(JdbcTemplate jdbcTemplate) { this.jdbcTemplate = jdbcTemplate; } /** * 要么没有,要么只有唯一,根据Id查询 **/ @Override public Account findAccountById(int id) { List<Account> accountList = jdbcTemplate.query("select * from account where id=?", new BeanPropertyRowMapper<>(Account.class), id); return accountList.isEmpty()?null:accountList.get(0); } @Override public List<Account> findAllCount() { List<Account> list = jdbcTemplate.query("select * from account", new BeanPropertyRowMapper<>(Account.class)); return list.isEmpty()?null:list; } @Override public Account findByName(String name) { List<Account> accounts = jdbcTemplate.query("select * from account where name=?", new BeanPropertyRowMapper<>(Account.class), name); if (accounts.isEmpty()) return null; if (accounts.size() > 1) throw new RuntimeException("结果集不唯一..."); return accounts.get(0); } @Override public int updateAccount(Account account) { return jdbcTemplate.update("update account set money=?,name=? where id=?",account.getMoney(),account.getName(),account.getId()); } }
-
AccountServiceImpl 代码
public class AccountServiceImpl implements AccountService { private AccountDao accountDao; public void setAccountDao(AccountDao accountDao) { this.accountDao = accountDao; } @Override public Account findAccountById(int id) { return accountDao.findAccountById(id); } @Override public List<Account> findAllCount() { return accountDao.findAllCount(); } @Override public int updateAccount(Account account) { return 0; } @Override public void transfer(String sourceName, String targetName, int money) { //根据名称查询转入转出账户 Account source = accountDao.findByName(sourceName); Account target = accountDao.findByName(targetName); //转出账户减钱 转入账户加钱 source.setMoney(source.getMoney()-money); target.setMoney(target.getMoney()+money); //更新转入转出账户 accountDao.updateAccount(source); int i = 1/0; accountDao.updateAccount(target); } }
-
applicationContext.xml 配置
<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:tx="http://www.springframework.org/schema/tx" 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/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"> <bean id="accountDaoImpl" class="com.lhg.spring.dao.impl.AccountDaoImpl"> <property name="jdbcTemplate" ref="jdbcTemplate" /> </bean> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate" > <property name="dataSource" ref="dataSource" /> </bean> <bean id="accountServiceImpl" class="com.lhg.spring.service.impl.AccountServiceImpl"> <property name="accountDao" ref="accountDaoImpl"/> </bean> <!--配置数据源--> <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://192.168.25.132:3306/ssm" /> <property name="username" value="root"/> <property name="password" value="root"/> </bean> <!--配置事务管理器--> <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource" /> </bean> <!--配置事务通知--> <tx:advice id="txAdvice" transaction-manager="txManager"> <tx:attributes> <!--配置事务的属性: isolation:用于指定事务的隔离级别,默认值是DEFAULT,表示使用数据库的默认隔离级别 propagation:用于指定事务的传播行为,默认值是REQUERD,表示一定会有事务,增删改的选择,查询方法可以使用SUPPORTS read-only:用于指定事务是否只读,只有查询方法才能设置为true,默认值是false,表示读写 rollback-for:用于指定一个异常,当该异常产生时,事务回滚,产生其它异常时事务不回滚,没有默认值,表示任何异常都回滚 no-rollback-for:用于指定一个异常,当该异常产生时事务不回滚,产生其它异常时事务回滚,没有默认值,表示任何异常都回滚 --> <tx:method name="*" propagation="REQUIRED" read-only="false"/><!--通用匹配--> <tx:method name="find*" propagation="SUPPORTS" read-only="true" /><!--匹配以find开头的方法,优先级更高--> </tx:attributes> </tx:advice> <aop:config> <!--配置切入点表达式--> <aop:pointcut id="pt" expression="execution(* com.lhg.spring.service.impl.*.*(..))"/> <!--建立切入点表达式与事务通知的对应关系--> <aop:advisor advice-ref="txAdvice" pointcut-ref="pt" /> </aop:config> </beans>
-
测试
public class XMLTest { public static void main(String[] args) { ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml"); AccountService accountServiceImpl = (AccountService) ac.getBean("accountServiceImpl"); accountServiceImpl.transfer("a","b",500); } }
-
效果
实战–基于注解的声明式事务控制
-
导入相关依赖,测试 jar 包…略
-
service 层
@Service @Transactional(propagation = Propagation.SUPPORTS,readOnly = true)//只读型事务的配置,而我们的转账需要的是读写型事务的配置 public class AccountServiceImpl implements AccountService { @Autowired private AccountDao accountDao; @Transactional(propagation = Propagation.REQUIRED,readOnly = false) @Override public void transfer(String sourceName, String targetName, int money) { System.out.println("start transfer..."); //根据名称查询转入转出账户 Account source = accountDao.findByName(sourceName); Account target = accountDao.findByName(targetName); //转出账户减钱 转入账户加钱 source.setMoney(source.getMoney()-money); target.setMoney(target.getMoney()+money); //更新转入转出账户 accountDao.updateAccount(source); int i = 1/0; accountDao.updateAccount(target); } }
-
dao 层
@Repository public class AccountDaoImpl implements AccountDao { @Autowired private JdbcTemplate jdbcTemplate; @Override public Account findByName(String name) { List<Account> accounts = jdbcTemplate.query("select * from account where name=?", new BeanPropertyRowMapper<>(Account.class), name); if (accounts.isEmpty()) return null; if (accounts.size() > 1) throw new RuntimeException("结果集不唯一..."); return accounts.get(0); } @Override public int updateAccount(Account account) { return jdbcTemplate.update("update account set money=?,name=? where id=?",account.getMoney(),account.getName(),account.getId()); } }
-
applicationContext.xml
<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:tx="http://www.springframework.org/schema/tx" 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/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"> <context:component-scan base-package="com.lhg.spring" /> <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://192.168.25.132:3306/ssm" /> <property name="username" value="root"/> <property name="password" value="root"/> </bean> <!--配置事务管理器--> <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource" /> </bean> <!--开启spring对注解事务的支持--> <tx:annotation-driven transaction-manager="txManager" /> </beans>
-
单元测试
@RunWith(SpringJUnit4ClassRunner.class)//使得Test.java拥有IOC环境 //@ContextConfiguration(classes = SpringConfig.class)//纯注解 @ContextConfiguration(locations = "classpath:applicationContext.xml")//注意写法 public class Test { @Autowired private AccountService accountServiceImpl; @org.junit.Test public void test(){ accountServiceImpl.transfer("a","b",500); } }
-
效果:转账出现异常时,事务成功回滚
实战–基于纯注解的声明式事务控制
-
service 层和 dao 层 不变,使用注解注入
-
新建一个 config 的包,在里面分别建三个 java 文件
-
SpringConfig.java
/** * spring的配置类,相当于applicationContext.xml */ @Configuration @ComponentScan("com.lhg.spring.*") @Import({JdbcConfig.class, TransactionConfig.class})//引入其它的配置 @PropertySource("db.properties")//读取数据库配置文件注解 @EnableTransactionManagement//开启对注解的支持,替换xml中<tx:annotation-driven transaction-manager="txManager" /> public class SpringConfig { }
-
JdbcConfig.java
/** * 和连接数据库相关的配置类 */ public class JdbcConfig { @Value("${jdbc.driver}") private String driver; @Value("${jdbc.url}") private String url; @Value("${jdbc.username}") private String username; @Value("${jdbc.password}") private String password; /** * 创建jdbctemplate对象,但此时没有进容器,要想进容器,需要一个bean注解 */ @Bean(name="jdbcTemplate")//是name不是value public JdbcTemplate createJdbcTemplate(DataSource dataSource){ return new JdbcTemplate(dataSource); } @Bean(name="dataSource") public DataSource createDataSource(){ DriverManagerDataSource dataSource = new DriverManagerDataSource(); dataSource.setDriverClassName(driver); dataSource.setUrl(url); dataSource.setUsername(username); dataSource.setPassword(password); return dataSource; } }
-
TransactionConfig.java
/** * 和事务相关的配置类,替换xml中 * <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> * <property name="dataSource" ref="dataSource" /> * </bean> */ public class TransactionConfig { @Bean(name="transactionManager") public PlatformTransactionManager createTransactionManager(DataSource dataSource){ return new DataSourceTransactionManager(dataSource); } }
-
-
测试结果:有异常时事务回滚,没异常时转账成功
/** * 使用 Junit 单元测试,测试我们的配置 * Srping 整合 junit 的配置 * 1、导入 jar 包 * 2、使用 Junit 提供的注解把 main 方法替换了,替换成 spring 提供的 @RunWith * 3、告知 spring 运行器,spring 和 IOC 创建时基于 xml 还是 注解,并且说明位置 * @ContextConfiguration * locations:指定 xml 文件的位置,加上 classpath关键字表示在类路径下 * classes:指定注解类所在位置 */ @RunWith(SpringJUnit4ClassRunner.class)//使得Test.java拥有IOC环境 @ContextConfiguration(classes = SpringConfig.class)//纯注解 //@ContextConfiguration(locations = "classpath:applicationContext.xml") public class Test { @Autowired private AccountService accountServiceImpl; @org.junit.Test public void test(){ accountServiceImpl.transfer("a","b",500); } }