朱晔和你聊Spring系列S1E6:容易犯错的Spring AOP (2)

这样程序启动后就会有一个PERSON表,表里有一条ID为1的记录。
通过启动器使用Mybatis非常简单,无需进行任何配置,建一个Mapper接口:

package me.josephzhu.spring101aop; import org.apache.ibatis.annotations.Insert; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Select; import java.util.List; @Mapper public interface DbMapper { @Select("SELECT COUNT(0) FROM PERSON") int personCount(); @Insert("INSERT INTO PERSON (NAME, AGE, BALANCE) VALUES ('zhuye', 35, 1000)") void personInsertWithoutId(); @Insert("INSERT INTO PERSON (ID, NAME, AGE, BALANCE) VALUES (1,'zhuye', 35, 1000)") void personInsertWithId(); @Select("SELECT * FROM PERSON") List<MyBean> getPersonList(); }

这里我们定义了4个方法:

查询表中记录数的方法

查询表中所有数据的方法

带ID字段插入数据的方法,由于程序启动的时候已经初始化了一条数据,如果这里我们再插入ID为1的记录显然会出错,用来之后测试事务使用

不带ID字段插入数据的方法
为了我们可以观察到数据库连接是否被Spring纳入事务管理,我们在application.properties配置文件中设置mybatis的Spring事务日志级别为DEBUG:

logging.level.org.mybatis.spring.transaction=DEBUG

现在我们来创建服务接口:

package me.josephzhu.spring101aop; import java.time.Duration; import java.util.List; public interface MyService { void insertData(boolean success); List<MyBean> getData(MyBean myBean, int count, Duration delay); }

定义了插入数据和查询数据的两个方法,下面是实现:

package me.josephzhu.spring101aop; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.time.Duration; import java.util.List; import java.util.stream.Collectors; import java.util.stream.IntStream; @Service public class MyServiceImpl implements MyService { @Autowired private DbMapper dbMapper; @Transactional(rollbackFor = Exception.class) public void _insertData(boolean success){ dbMapper.personInsertWithoutId(); if(!success) dbMapper.personInsertWithId(); } @Override public void insertData(boolean success) { try { _insertData(success); } catch (Exception ex) { ex.printStackTrace(); } System.out.println("记录数:" + dbMapper.personCount()); } @Override public List<MyBean> getData(MyBean myBean, int count, Duration delay) { try { Thread.sleep(delay.toMillis()); } catch (InterruptedException e) { e.printStackTrace(); } return IntStream.rangeClosed(1,count) .mapToObj(i->new MyBean((long)i,myBean.getName() + i, myBean.getAge(), myBean.getBalance())) .collect(Collectors.toList()); } }

getData方法我们就不细说了,只是实现了休眠然后根据传入的myBean作为模板组装了count条测试数据返回。我们来重点看一下insertData方法,这就是使用Spring AOP的一个坑了。看上去配置啥的都没问题,但是_insertData是不能生效自动事务管理的。

我们知道Spring AOP使用代理目标对象方式实现AOP,在从外部调用insertData方法的时候其实走的是代理,这个时候事务环绕可以生效,在方法内部我们通过this引用调用_insertData方法,虽然方法外部我们设置了Transactional注解,但是由于走的不是代理调用,Spring AOP自然无法通过AOP增强为我们做事务管理。

我们来创建主程序测试一下:

package me.josephzhu.spring101aop; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.CommandLineRunner; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.AdviceMode; import org.springframework.context.annotation.Configuration; import org.springframework.transaction.annotation.EnableTransactionManagement; import java.math.BigDecimal; import java.time.Duration; @SpringBootApplication public class Spring101AopApplication implements CommandLineRunner { public static void main(String[] args) { SpringApplication.run(Spring101AopApplication.class, args); } @Autowired private MyService myService; @Override public void run(String... args) throws Exception { myService.insertData(true); myService.insertData(false); System.out.println(myService.getData(new MyBean(0L, "zhuye",35, new BigDecimal("1000")), 5, Duration.ofSeconds(1))); } }

在Runner中,我们使用true和false调用了两次insertData方法。后面一次调用肯定会失败,因为_insert方法中会进行重复ID的数据插入。运行程序后得到如下输出:

2018-10-07 09:11:44.605 INFO 19380 --- [ main] m.j.s.Spring101AopApplication : Started Spring101AopApplication in 3.072 seconds (JVM running for 3.74) 2018-10-07 09:11:44.621 DEBUG 19380 --- [ main] o.m.s.t.SpringManagedTransaction : JDBC Connection [HikariProxyConnection@2126664214 wrapping conn0: url=jdbc:h2:mem:testdb user=SA] will not be managed by Spring 2018-10-07 09:11:44.626 DEBUG 19380 --- [ main] o.m.s.t.SpringManagedTransaction : JDBC Connection [HikariProxyConnection@775174220 wrapping conn0: url=jdbc:h2:mem:testdb user=SA] will not be managed by Spring 记录数:2 2018-10-07 09:11:44.638 DEBUG 19380 --- [ main] o.m.s.t.SpringManagedTransaction : JDBC Connection [HikariProxyConnection@2084486251 wrapping conn0: url=jdbc:h2:mem:testdb user=SA] will not be managed by Spring 2018-10-07 09:11:44.638 DEBUG 19380 --- [ main] o.m.s.t.SpringManagedTransaction : JDBC Connection [HikariProxyConnection@26418585 wrapping conn0: url=jdbc:h2:mem:testdb user=SA] will not be managed by Spring 2018-10-07 09:11:44.642 INFO 19380 --- [ main] o.s.b.f.xml.XmlBeanDefinitionReader : Loading XML bean definitions from class path resource [org/springframework/jdbc/support/sql-error-codes.xml] org.springframework.dao.DuplicateKeyException: ### Error updating database. Cause: org.h2.jdbc.JdbcSQLException: Unique index or primary key violation: "PRIMARY KEY ON PUBLIC.PERSON(ID)"; SQL statement: INSERT INTO PERSON (ID, NAME, AGE, BALANCE) VALUES (1,'zhuye', 35, 1000) [23505-197] 2018-10-07 09:11:44.689 DEBUG 19380 --- [ main] o.m.s.t.SpringManagedTransaction : JDBC Connection [HikariProxyConnection@529949842 wrapping conn0: url=jdbc:h2:mem:testdb user=SA] will not be managed by Spring 记录数:3 [MyBean(id=1, name=zhuye1, age=35, balance=1000), MyBean(id=2, name=zhuye2, age=35, balance=1000), MyBean(id=3, name=zhuye3, age=35, balance=1000), MyBean(id=4, name=zhuye4, age=35, balance=1000), MyBean(id=5, name=zhuye5, age=35, balance=1000)]

从日志的几处我们都可以得到结论,事务管理没有生效:

我们可以看到有类似Connection will not be managed by Spring的提示,说明连接没有进入Spring的事务管理。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zzfgzw.html