摘要
学习Spring的JdbcTemplate,以及Spring基于XML或者注解的声明式事务控制
正文
目录
Spring的IOC和DI Spring的AOP Spring的事务控制 源码 一、JdbcTemplate 1.1 概述 JdbcTemplate是spring框架中提供的一个对象,是对原始Jdbc API对象的简单封装。
Spring框架提供的工具类
关系型数据库JdbcTemplate HibernateTemplate 非关系型数据库 操作消息队列 JdbcTemplate的作用:它就是与数据库交互的,实现对表的CRUD操作
1.2 使用 1.2.1 简单使用 可以直接用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class JdbcTemplateDemo01 {
public static void main ( String [] args ) {
//准备数据源:Spring的内置数据源
DriverManagerDataSource ds = new DriverManagerDataSource ();
ds . setDriverClassName ( "com.mysql.cj.jdbc.Driver" );
ds . setUrl ( "jdbc:mysql://localhost:3306/spring?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai" );
ds . setUsername ( "root" );
ds . setPassword ( "root" );
//1.创建JdbcTemplate对象
JdbcTemplate jt = new JdbcTemplate ();
jt . setDataSource ( ds );
//2. 执行操作
jt . execute ( "insert into account(name,money)values('ccc',1000)" );
}
}
1.2.2 Spring注解使用 Config
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Configuration
@ComponentScan ( "top.meethigher.demo19" )
@EnableAspectJAutoProxy
public class Config {
@Bean ( "ds" )
public DataSource createDataSource (){
DriverManagerDataSource ds = new DriverManagerDataSource ();
ds . setDriverClassName ( "com.mysql.cj.jdbc.Driver" );
ds . setUrl ( "jdbc:mysql://localhost:3306/spring?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai" );
ds . setUsername ( "root" );
ds . setPassword ( "root" );
return ds ;
}
@Bean ( "jdbcTemplate" )
public JdbcTemplate createJdbcTemplate ( @Qualifier ( "ds" ) DataSource ds ){
return new JdbcTemplate ( ds );
}
}
Account
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
public class Account implements Serializable {
private Integer id ;
private String name ;
private Float money ;
@Override
public String toString () {
return "Account{" +
"id=" + id +
", name='" + name + '\'' +
", money=" + money +
'}' ;
}
public Integer getId () {
return id ;
}
public void setId ( Integer id ) {
this . id = id ;
}
public String getName () {
return name ;
}
public void setName ( String name ) {
this . name = name ;
}
public Float getMoney () {
return money ;
}
public void setMoney ( Float money ) {
this . money = money ;
}
}
测试
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class JdbcTemplateDemo02 {
public static void main ( String [] args ) {
AnnotationConfigApplicationContext aac = new AnnotationConfigApplicationContext ( Config . class );
// JdbcTemplate jdbcTemplate = (JdbcTemplate) aac.getBean("jdbcTemplate");
JdbcTemplate jdbcTemplate = aac . getBean ( "jdbcTemplate" , JdbcTemplate . class );
//插入
// jdbcTemplate.execute("insert into account(name,money)values('ddd',2222)");
//修改
// jdbcTemplate.update("update account set name=?,money=? where id=?","bbb",111,13);
//删除
// jdbcTemplate.update("delete from account where id=?",13);
//查询所有
// List<Account> query = jdbcTemplate.query("select * from account", new BeanPropertyRowMapper<>(Account.class));
// System.out.println(query);
//查询一个
// List<Account> query = jdbcTemplate.query("select * from account where id=?", new BeanPropertyRowMapper<>(Account.class), 1);
// System.out.println(query.get(0));
//查询总记录数
Long aLong = jdbcTemplate . queryForObject ( "select count(*) from account where money>?" , Long . class , 1000 );
System . out . println ( aLong );
}
}
1.2.3 Spring与Dao的联合使用的两种方式 使用注解的dao层来实现 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
@Component ( "accoundDao" )
public class AccountDaoImpl implements IAccountDao {
@Autowired
private JdbcTemplate jdbcTemplate ;
@Override
public Account findAccountById ( Integer accountId ) {
List < Account > query = jdbcTemplate . query ( "select * from account where id=?" , new BeanPropertyRowMapper <> ( Account . class ), accountId );
if ( query . isEmpty ()){
return null ;
}
return query . get ( 0 );
}
@Override
public Account findAccountByName ( String name ) {
List < Account > query = jdbcTemplate . query ( "select * from account where name=?" , new BeanPropertyRowMapper <> ( Account . class ), name );
if ( query . isEmpty ()){
return null ;
}
if ( query . size () > 1 ){
throw new RuntimeException ( "结果不唯一" );
}
return query . get ( 0 );
}
@Override
public void updateAccount ( Account account ) {
jdbcTemplate . update ( "update account set name=?,money=? where id=?" , account . getName (), account . getMoney (), account . getId ());
}
}
使用xml的dao层来实现 但是如果有很多个dao的话,就会有重复代码了,那么如何解决?
我们可以通过创建一个dao的共有父类,让其他子类继承 。
而spring中,提供了这种个父类JdbcDaoSupport
AccountDaoImpl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class AccountDaoImpl extends JdbcDaoSupport implements IAccountDao {
@Override
public Account findAccountById ( Integer accountId ) {
List < Account > query = super . getJdbcTemplate (). query ( "select * from account where id=?" , new BeanPropertyRowMapper <> ( Account . class ), accountId );
if ( query . isEmpty ()){
return null ;
}
return query . get ( 0 );
}
@Override
public Account findAccountByName ( String name ) {
List < Account > query = super . getJdbcTemplate (). query ( "select * from account where name=?" , new BeanPropertyRowMapper <> ( Account . class ), name );
if ( query . isEmpty ()){
return null ;
}
if ( query . size () > 1 ){
throw new RuntimeException ( "结果不唯一" );
}
return query . get ( 0 );
}
@Override
public void updateAccount ( Account account ) {
super . getJdbcTemplate (). update ( "update account set name=?,money=? where id=?" , account . getName (), account . getMoney (), account . getId ());
}
}
spring.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?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
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd" >
<bean id= "accountDao" class= "top.meethigher.demo20.dao.impl.AccountDaoImpl" >
<property name= "dataSource" ref= "ds" />
</bean>
<bean id= "ds" class= "org.springframework.jdbc.datasource.DriverManagerDataSource" >
<property name= "driverClassName" value= "com.mysql.cj.jdbc.Driver" ></property>
<property name= "url" value= "jdbc:mysql://localhost:3306/spring?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&useSSL=false" ></property>
<property name= "username" value= "root" ></property>
<property name= "password" value= "root" ></property>
</bean>
</beans>
测试类
1
2
3
4
5
6
7
8
9
public class JdbcTemplateDemo03 {
public static void main ( String [] args ) {
ClassPathXmlApplicationContext aac = new ClassPathXmlApplicationContext ( "spring.xml" );
AccountDaoImpl accoundDao = aac . getBean ( "accountDao" , AccountDaoImpl . class );
System . out . println ( accoundDao . findAccountById ( 1 ));
// System.out.println(accoundDao.findAccountByName("水冰儿"));
System . out . println ( accoundDao . findAccountByName ( "小舞" ));
}
}
二、事务控制 2.1 转账案例再次优化 如果使用环绕通知,只需要修改如下即可。会自动将impl包下的类作为切入点,进行增强
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
@Component ( "tsManager" )
@Aspect
public class TransactionManager {
@Autowired
private ConnectionUtils connectionUtils ;
@Pointcut ( "execution(* top.meethigher.demo21.service.impl.*.*(..))" )
private void pt (){}
/**
* 开启手动提交事务
*/
// @Before("pt()")
public void beginTransaction (){
try {
connectionUtils . getThreadConnection (). setAutoCommit ( false );
System . out . println ( "开启手动提交事务" );
} catch ( SQLException e ) {
e . printStackTrace ();
}
}
/**
* 提交事务
*/
// @AfterReturning("pt()")
public void commitTransaction (){
try {
connectionUtils . getThreadConnection (). commit ();
System . out . println ( "提交事务" );
} catch ( SQLException e ) {
e . printStackTrace ();
}
}
/**
* 回滚事务
*/
// @AfterThrowing("pt()")
public void rollbackTransaction (){
try {
connectionUtils . getThreadConnection (). rollback ();
System . out . println ( "回滚事务" );
} catch ( SQLException e ) {
e . printStackTrace ();
}
}
/**
* 释放连接
*/
// @After("pt()")
public void release (){
try {
connectionUtils . getThreadConnection (). close (); //归还连接
System . out . println ( "归还连接" );
} catch ( SQLException e ) {
e . printStackTrace ();
}
}
/**
* 环绕通知实现增强
* @param proceedingJoinPoint
* @return
*/
@Around ( "pt()" )
public Object around ( ProceedingJoinPoint proceedingJoinPoint ){
Object returnValue = null ;
try {
beginTransaction ();
System . out . println ( "前置通知->开启事务" );
returnValue = proceedingJoinPoint . proceed ( proceedingJoinPoint . getArgs ());
commitTransaction ();
System . out . println ( "后置通知->提交事务" );
return returnValue ;
} catch ( Throwable t ){
rollbackTransaction ();
System . out . println ( "异常通知->回滚事务" );
throw new RuntimeException ( t );
} finally {
release ();
System . out . println ( "最终通知->释放连接" );
}
}
}
此时我们就需要思考了,这是使用QueryRunner实现的,如果是JdbcTemplate,如何实现事务控制呢?
2.2 spring声明式事务 2.2.1 基于xml的声明式事务 步骤
配置事务管理器
spring提供的DataSourceTransactionManager 配置事务通知
需要导入事务的约束。官方文档DataAccess,搜索xmlns:tx(同时也需要aop的) 使用tx:advice标签配置事务通知id:事务通知的唯一标志 transaction-manager:给事务通知提供一个事务管理器引用 配置aop中的通用切入点表达式:aop:pointcut
建立事务通知和切入点表达式的对应关系:aop:advisor
配置事务的属性:在tx:advice的内部下的tx:method
name:方法名称。可以使用通配符*表示所有。如果名称规范的话,findxxx,像jpa那样,可以这么统配find*。*与find*,后者优先级更高
isolation:用于指定事务的隔离级别,默认级别DEFAULT ,表示使用数据库的默认隔离级别
no-rollback-for:用于指定一个异常,当产生该异常时,事务不回滚;产生其他异常时,事务回滚。没有默认值,表示任何异常都回滚。
rollback-for:用于指定一个异常,当产生该异常时,事务回滚;产生其他异常时,不回滚。没有默认值,表示任何异常都回滚
propagation:用于指定事务的传播行为。默认值是REQUIRED ,表示一定会有事务,增删改时选择。查询方法可以选择SUPPORTS
read-only:用于指定事务事务是否只读,只有查询方法才能设置为true,默认值是false ,表示读写
timeout:用于指定事务的超时时间,默认值是-1,表示永不超时。如果指定了数值,以秒为单位
配置文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<?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
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx
https://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd" >
<bean id= "accountDao" class= "top.meethigher.demo21.dao.impl.AccountDaoImpl" >
<property name= "dataSource" ref= "ds" />
</bean>
<bean id= "accountService" class= "top.meethigher.demo21.service.impl.AccountServiceImpl" >
<property name= "accountDao" ref= "accountDao" />
</bean>
<bean id= "ds" class= "org.springframework.jdbc.datasource.DriverManagerDataSource" >
<property name= "driverClassName" value= "com.mysql.cj.jdbc.Driver" />
<property name= "url"
value= "jdbc:mysql://localhost:3306/spring?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&useSSL=false" />
<property name= "username" value= "root" />
<property name= "password" value= "root" />
</bean>
<!--配置事务-->
<bean id= "transactionManager" class= "org.springframework.jdbc.datasource.DataSourceTransactionManager" >
<property name= "dataSource" ref= "ds" />
</bean>
<!--配置事务通知-->
<tx:advice id= "txAdvice" transaction-manager= "transactionManager" >
<!--配置事务属性-->
<tx:attributes>
<tx:method name= "transfer" propagation= "REQUIRED" read-only= "false" />
</tx:attributes>
</tx:advice>
<!--配置aop-->
<aop:config>
<aop:pointcut id= "pt" expression= "execution(* top.meethigher.demo21.service.impl.*.*(..))" />
<!--建立切入点表达式和事务通知的对应关系-->
<aop:advisor advice-ref= "txAdvice" pointcut-ref= "pt" />
</aop:config>
</beans>
AccountDaoImpl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class AccountDaoImpl extends JdbcDaoSupport implements AccountDao {
@Override
public Account findAccountByName ( String name ) {
List < Account > query = super . getJdbcTemplate (). query (
"select * from account where name=?" ,
new BeanPropertyRowMapper <> ( Account . class ),
name );
if ( ! query . isEmpty ()) {
return query . get ( 0 );
}
return null ;
}
@Override
public void transfer ( String sourceName , String targetName , Float money ) {
Account account1 = findAccountByName ( sourceName );
Account account2 = findAccountByName ( targetName );
account1 . setMoney ( account1 . getMoney () - money );
account2 . setMoney ( account2 . getMoney () + money );
update ( account1 );
int a = 2 / 0 ;
update ( account2 );
}
@Override
public void update ( Account account ) {
super . getJdbcTemplate (). update (
"update account set money=? where id=?" ,
account . getMoney (), account . getId ()
);
}
}
测试类
1
2
3
4
5
6
7
8
9
10
11
@RunWith ( SpringJUnit4ClassRunner . class )
@ContextConfiguration ( locations = "classpath:spring.xml" )
public class Test21 {
@Autowired
private AccountService accountService ;
@Test
public void testTransfer () {
accountService . transfer ( "白沉香" , "小舞" , 500f );
}
}
2.2.2 基于注解与xml的声明式事务 步骤
配置事务管理器 开启spring对**注解事务 **的支持 在需要事务支持的地方,使用@Transactional注解 配置文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<?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"
xmlns:context= "http://www.springframework.org/schema/context"
xsi:schemaLocation= "
http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx
https://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd" >
<context:component-scan base-package= "top.meethigher.demo22" />
<bean id= "accountDao" class= "top.meethigher.demo22.dao.impl.AccountDaoImpl" />
<bean id= "accountService" class= "top.meethigher.demo22.service.impl.AccountServiceImpl" />
<bean id= "ds" class= "org.springframework.jdbc.datasource.DriverManagerDataSource" >
<property name= "driverClassName" value= "com.mysql.cj.jdbc.Driver" />
<property name= "url"
value= "jdbc:mysql://localhost:3306/spring?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&useSSL=false" />
<property name= "username" value= "root" />
<property name= "password" value= "root" />
</bean>
<bean id= "jdbcTemplate" class= "org.springframework.jdbc.core.JdbcTemplate" >
<property name= "dataSource" ref= "ds" />
</bean>
<!--配置事务-->
<bean id= "transactionManager" class= "org.springframework.jdbc.datasource.DataSourceTransactionManager" >
<property name= "dataSource" ref= "ds" />
</bean>
<!--开启spring对注解事务的支持-->
<tx:annotation-driven transaction-manager= "transactionManager" />
</beans>
AccountDaoImpl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
@Repository ( "accountDao" )
@Transactional ( readOnly = true ) //配置为只读事务,也就是查询
public class AccountDaoImpl implements AccountDao {
@Autowired
private JdbcTemplate jdbcTemplate ;
@Override
public Account findAccountByName ( String name ) {
List < Account > query = jdbcTemplate . query (
"select * from account where name=?" ,
new BeanPropertyRowMapper <> ( Account . class ),
name );
if ( ! query . isEmpty ()) {
return query . get ( 0 );
}
return null ;
}
@Transactional ( readOnly = false ) //修改为读写事务,也就是增删改
@Override
public void transfer ( String sourceName , String targetName , Float money ) {
Account account1 = findAccountByName ( sourceName );
Account account2 = findAccountByName ( targetName );
account1 . setMoney ( account1 . getMoney () - money );
account2 . setMoney ( account2 . getMoney () + money );
update ( account1 );
int a = 2 / 0 ;
update ( account2 );
}
@Override
public void update ( Account account ) {
jdbcTemplate . update (
"update account set money=? where id=?" ,
account . getMoney (), account . getId ()
);
}
}
AccountServiceImpl
1
2
3
4
5
6
7
8
9
10
@Service ( "accountService" )
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountDao accountDao ;
@Override
public void transfer ( String sourceName , String targetName , float money ) {
accountDao . transfer ( sourceName , targetName , money );
}
}
测试类
1
2
3
4
5
6
7
8
9
10
11
@RunWith ( SpringJUnit4ClassRunner . class )
@ContextConfiguration ( locations = "classpath:spring.xml" )
public class Test22 {
@Autowired
private AccountService accountService ;
@Test
public void testTransfer () {
accountService . transfer ( "白沉香" , "小舞" , 500f );
}
}
2.2.3 基于注解的声明式事务 替代xml中的开启spring对**注解事务 **的支持
@EnableTransactionManagement jdbc.properties
1
2
3
4
driver = com.mysql.cj.jdbc.Driver
url = jdbc:mysql://localhost:3306/spring?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
u = root
pw = 1050121804
配置类
JdbcConfig
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@Configuration
@PropertySource ( "classpath:jdbc.properties" )
public class JdbcConfig {
@Value ( "${u}" )
private String USER ;
@Value ( "${pw}" )
private String PASS ;
@Value ( "${driver}" )
private String DRIVER ;
@Value ( "${url}" )
private String URL ;
@Bean ( "jdbcTemplate" )
public JdbcTemplate createJdbcTemplate ( @Qualifier ( "ds" ) DataSource ds ){
return new JdbcTemplate ( ds );
}
@Bean ( "ds" )
public DataSource createDataSource (){
DriverManagerDataSource ds = new DriverManagerDataSource ();
ds . setUsername ( USER );
ds . setPassword ( PASS );
ds . setDriverClassName ( DRIVER );
ds . setUrl ( URL );
return ds ;
}
}
TransactionManagerConfig
1
2
3
4
5
6
7
@Configuration
public class TransactionManagerConfig {
@Bean ( "transactionManager" )
public PlatformTransactionManager createTransactionManager ( @Qualifier ( "ds" ) DataSource ds ){
return new DataSourceTransactionManager ( ds );
}
}
SpringConfig
1
2
3
4
5
6
@Configuration
@ComponentScan ( basePackages = "top.meethigher.demo23" )
@Import ({ JdbcConfig . class , TransactionManagerConfig . class })
@EnableTransactionManagement //开启对事务的支持
public class SpringConfig {
}
测试类
1
2
3
4
5
6
7
8
9
10
11
@RunWith ( SpringJUnit4ClassRunner . class )
@ContextConfiguration ( classes = SpringConfig . class )
public class Test23 {
@Autowired
private AccountService accountService ;
@Test
public void testTransfer () {
accountService . transfer ( "白沉香" , "小舞" , 500f );
}
}
其他的跟上一节的一样。
2.3 spring编程式事务 需要在配置里添加事务模板对象,用于对指定方法进行添加事务
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<?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"
xmlns:context= "http://www.springframework.org/schema/context"
xsi:schemaLocation= "
http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx
https://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd" >
<context:component-scan base-package= "top.meethigher.demo24" />
<bean id= "accountDao" class= "top.meethigher.demo24.dao.impl.AccountDaoImpl" >
<property name= "dataSource" ref= "ds" />
</bean>
<bean id= "accountService" class= "top.meethigher.demo24.service.impl.AccountServiceImpl" >
<property name= "accountDao" ref= "accountDao" />
<property name= "transactionTemplate" ref= "transactionTemplate" />
</bean>
<bean id= "ds" class= "org.springframework.jdbc.datasource.DriverManagerDataSource" >
<property name= "driverClassName" value= "com.mysql.cj.jdbc.Driver" />
<property name= "url"
value= "jdbc:mysql://localhost:3306/spring?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&useSSL=false" />
<property name= "username" value= "root" />
<property name= "password" value= "1050121804" />
</bean>
<!--配置事务管理器-->
<bean id= "transactionManager" class= "org.springframework.jdbc.datasource.DataSourceTransactionManager" >
<property name= "dataSource" ref= "ds" />
</bean>
<!--事务模板对象-->
<bean id= "transactionTemplate" class= "org.springframework.transaction.support.TransactionTemplate" >
<property name= "transactionManager" ref= "transactionManager" />
</bean>
</beans>
其他类都一样,唯一不同的是对要方法进行添加事务
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class AccountServiceImpl implements AccountService {
private TransactionTemplate transactionTemplate ;
private AccountDao accountDao ;
public void setTransactionTemplate ( TransactionTemplate transactionTemplate ) {
this . transactionTemplate = transactionTemplate ;
}
public void setAccountDao ( AccountDao accountDao ) {
this . accountDao = accountDao ;
}
@Override
public void transfer ( String sourceName , String targetName , float money ) {
transactionTemplate . execute ( new TransactionCallback < Object > () {
@Override
public Object doInTransaction ( TransactionStatus transactionStatus ) {
accountDao . transfer ( sourceName , targetName , money );
return null ;
}
});
}
}
展开
缺点:让方法实现事务功能时,都需要写这么多,太麻烦了。还是注解的声明式事务用的多一点,只需要配置事务并添加@Transaction注解即可。
三、多数据库事务控制 数据库的事务是基于连接 Connection 来管理的。如果操作多个数据库或者多个连接,只要保证连接一起 commit 或者 一起 rollback 即可。源码meethigher/jdbc-transaction-spring-boot: JDBC多事务使用示例
但是这个操作在应用层实现,会存在一种问题。伪代码如下
1
2
3
4
5
6
7
8
9
try {
...
conn1 . commit ();
conn2 . commit ();
} catch ( Exception e ) {
conn1 . rollback ();
conn2 . rollback ();
}
conn1成功提交事务后,conn2如果没有提交成功。这时候conn1应该要回滚的,但是此时已经无法回滚了。
有两种解决方案
3.1 原生JDBC 示例代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import java.sql.Connection ;
import java.sql.DriverManager ;
import java.sql.Statement ;
import java.util.concurrent.locks.LockSupport ;
public class App {
public static Connection getConnection ( String jdbcUrl , String username , String password ) throws Exception {
Class . forName ( "org.postgresql.Driver" );
return DriverManager . getConnection ( jdbcUrl , username , password );
}
public static void main ( String [] args ) throws Exception {
Connection conn1 = getConnection ( "jdbc:postgresql://10.0.0.20:5432/first" , "postgres" , "postgres" );
Connection conn2 = getConnection ( "jdbc:postgresql://10.0.0.20:5432/second" , "postgres" , "postgres" );
conn1 . setAutoCommit ( false );
conn2 . setAutoCommit ( false );
Statement stat1 = conn1 . createStatement ();
Statement stat2 = conn2 . createStatement ();
stat1 . execute ( "create table conn(id varchar)" );
stat2 . execute ( "create table conn(id varchar)" );
// jdbc的事务,是针对单个Connection。如果需要复杂的事务,比如分布式,需要自己维护多个Connection的事务
conn1 . commit ();
conn2 . commit ();
// conn1.rollback();
// conn2.rollback();
LockSupport . park ();
}
}
3.2 Spring JdbcTemplate 依然保持 @Transactional 的用法
1
2
3
4
5
6
7
@Override
@Transactional ( rollbackFor = Exception . class )
public void run ( String ... args ) throws Exception {
firstJdbcTemplate . update ( "create table record(id varchar)" );
int i = 1 / 0 ;
secondJdbcTemplate . update ( "create table record(id varchar)" );
}
但是在配置时,需要将多个数据源的事务通过org.springframework.data.transaction.ChainedTransactionManager配置到一起。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
@Bean
public DataSource firstDataSource () {
HikariConfig config = new HikariConfig ();
config . setJdbcUrl ( "jdbc:postgresql://10.0.0.20:5432/first" );
config . setUsername ( "postgres" );
config . setPassword ( "postgres" );
return new HikariDataSource ( config );
}
@Bean
public JdbcTemplate firstJdbcTemplate ( @Qualifier ( "firstDataSource" ) DataSource dataSource ) {
return new JdbcTemplate ( dataSource );
}
@Bean
public DataSource secondDataSource () {
HikariConfig config = new HikariConfig ();
config . setJdbcUrl ( "jdbc:postgresql://10.0.0.20:5432/second" );
config . setUsername ( "postgres" );
config . setPassword ( "postgres" );
return new HikariDataSource ( config );
}
@Bean
public JdbcTemplate secondJdbcTemplate ( @Qualifier ( "secondDataSource" ) DataSource dataSource ) {
return new JdbcTemplate ( dataSource );
}
@Bean
public PlatformTransactionManager chainedTransactionManager (
@Qualifier ( "firstDataSource" ) DataSource d1 ,
@Qualifier ( "secondDataSource" ) DataSource d2
) {
return new ChainedTransactionManager ( new DataSourceTransactionManager ( d1 ), new DataSourceTransactionManager ( d2 ));
}