Spring整合JDBC以及AOP管理事务

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring整合JDBC以及AOP管理事务相关的知识,希望对你有一定的参考价值。

本节内容:

  • Spring整合JDBC
  • Spring中的AOP管理事务

 

一、Spring整合JDBC

Spring框架永远是一个容器,Spring整合JDBC其实就是Spring提供了一个对象,这个对象封装了JDBC技术,它可以操作数据库,这个对象可以放入Spring容器,交给Spring容器来管理。所以我们主要是要学习这个对象:JDBCTemplate。这个对象和DBUtils中的QueryRunner非常相似。

1. 导包

4+2+2(测试需要的包spring-test,新版本测试时还需要spring-aop包,junit4类库)+(操作数据库包:JDBC驱动包mysql-connector-java-5.1.7-bin.jar + c3p0连接池:com.springsource.com.mchange.v2.c3p0-0.9.1.2.jar)+ spring-jdbc(JDBCTemplate在这个包里)+ spring-tx(不管你用不用事务,操作数据库都要导入这个包)

 

2. 准备数据库

我这里使用Navicat创建一个数据库 ,在库里创建一张表。

技术分享图片

技术分享图片

 

3. 写代码测试(先自己写代码new JdbcTemplate)

创建一个包com.wisedu.jdbctemplate,在里面创建一个类Demo.java,使用JdbcTemplate:

技术分享图片
package com.wisedu.jdbctemplate;


import org.junit.Test;
import org.springframework.jdbc.core.JdbcTemplate;

import com.mchange.v2.c3p0.ComboPooledDataSource;


//演示JDBC模板
public class Demo {
    
    @Test
    public void fun1() throws Exception{

        //0 准备连接池
        ComboPooledDataSource dataSource = new ComboPooledDataSource();
        dataSource.setDriverClass("com.mysql.jdbc.Driver");
        dataSource.setJdbcUrl("jdbc:mysql:///spring");
        dataSource.setUser("root");
        dataSource.setPassword("123456");
        //1 创建JDBC模板对象,和使用QueryRunner一样,空参或者传一个连接池进去
        JdbcTemplate jt = new JdbcTemplate();
        jt.setDataSource(dataSource);
        //2 书写sql,并执行
        String sql = "insert into t_user values(null,‘rose‘) ";
        jt.update(sql);
        
    }
    
    
}
Demo.java

 

4. 使用Spring容器管理JdbcTemplate对象

上面的代码中没有用到Spring容器,对象都是我们手动new出来。

UserDao.java

package com.wisedu.jdbctemplate;

import java.util.List;
import com.wisedu.bean.User;

public interface UserDao {
	//增
	void save(User u);
	//删
	void delete(Integer id);
	//改
	void update(User u);
	//查
	User getById(Integer id);
	//查
	int getTotalCount();
	//查
	List<User> getAll();
}

UserDaoImpl.java

package com.wisedu.jdbctemplate;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;

import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;

import com.wisedu.bean.User;

//使用JDBC模板实现增删改查
public class UserDaoImpl implements UserDao {

	private JdbcTemplate jt;

	@Override
	public void save(User u) {
		String sql = "insert into t_user values(null,?) ";
                jt.update(sql, u.getName());
	}

	@Override
	public void delete(Integer id) {
		String sql = "delete from t_user where id = ? ";
                jt.update(sql, id);
	}

	@Override
	public void update(User u) {
		String sql = "update  t_user set name = ? where id=? ";
                jt.update(sql, u.getName(), u.getId());
	}

	@Override
	public User getById(Integer id) {
		String sql = "select * from t_user where id = ? ";
        return jt.queryForObject(sql, new RowMapper<User>() {
            @Override
            public User mapRow(ResultSet resultSet, int i) throws SQLException { //Spring会帮忙遍历ResultSet,不用判断,能进入这个方法,一定有值
                User u = new User();
                u.setId(resultSet.getInt("id"));
                u.setName(resultSet.getString("name"));

                return u;
            }
        }, id);

	}

	@Override
	public int getTotalCount() {
		String sql = "select count(*) from t_user  ";
		Integer count = jt.queryForObject(sql, Integer.class);

                  return count;
	}

	@Override
	public List<User> getAll() {
		String sql = "select * from t_user  ";
         List<User> list = jt.query(sql, new RowMapper<User>() {
            @Override
            public User mapRow(ResultSet resultSet, int i) throws SQLException {
                User u = new User();
                u.setId(resultSet.getInt("id"));
                u.setName(resultSet.getString("name"));

                return u;
            }
        });
		return list;
	}

    public void setJt(JdbcTemplate jt) {
        this.jt = jt;
    }
}    

写完代码之后,我们需要把 UserDaoImpl 配置到Spring容器中,交由Spring容器来管理。这个UserDaoImpl对象依赖 JdbcTemplate 对象,而 JdbcTemplate 对象依赖连接池,将连接池注入到 JdbcTemplate 中。

applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context-4.2.xsd ">
    
    
    <!-- 1.将连接池放入spring容器.
        这个连接池com.mchange.v2.c3p0.ComboPooledDataSource以前是我们自己手动new出来,现在是Spring帮我们new出来 -->
    
    <bean name="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" >
        <property name="jdbcUrl" value="jdbc:mysql:///spring" ></property>
        <property name="driverClass" value="com.mysql.jdbc.Driver" ></property>
        <property name="user" value="root" ></property>
        <property name="password" value="123456" ></property>
    </bean>
    
    
    <!-- 2.将JDBCTemplate放入spring容器 -->
    <bean name="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate" >
        <property name="dataSource" ref="dataSource" ></property> <!--set方式注入连接池-->
    </bean>
    
    <!-- 3.将UserDao放入spring容器 -->
    <bean name="userDao" class="com.wisedu.jdbctemplate.UserDaoImpl" >
        <property name="jt" ref="jdbcTemplate" ></property>
    </bean>
    
</beans>  

编写测试代码:

Demo.java

package com.wisedu.jdbctemplate;

import java.beans.PropertyVetoException;

import javax.annotation.Resource;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import com.mchange.v2.c3p0.ComboPooledDataSource;

import com.wisedu.bean.User;

//演示JDBC模板
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class Demo {
	@Resource(name="userDao")
	private UserDao ud;
	
	@Test
	public void fun1() throws Exception{
		//0 准备连接池
		ComboPooledDataSource dataSource = new ComboPooledDataSource();
		dataSource.setDriverClass("com.mysql.jdbc.Driver");
		dataSource.setJdbcUrl("jdbc:mysql:///spring");
		dataSource.setUser("root");
		dataSource.setPassword("123456");
		//1 创建JDBC模板对象,和使用QueryRunner一样,空参或者传一个连接池进去
		JdbcTemplate jt = new JdbcTemplate();
		jt.setDataSource(dataSource);
		//2 书写sql,并执行
		String sql = "insert into t_user values(null,‘rose‘) ";
		jt.update(sql);
		
	}
	
	@Test
	public void fun2() throws Exception{
		User u = new User();
		u.setName("tom");
		ud.save(u);
	}

	@Test
	public void fun3() throws Exception{
		User u = new User();
		u.setId(2);
		u.setName("jack");
		ud.update(u);
		
	}
	
	@Test
	public void fun4() throws Exception{
		ud.delete(2);
	}
	
	@Test
	public void fun5() throws Exception{
		System.out.println(ud.getTotalCount());
	}
	
	@Test
	public void fun6() throws Exception{
		System.out.println(ud.getById(1));
	}
	
	@Test
	public void fun7() throws Exception{
		System.out.println(ud.getAll());
	}
}
	

 

5. 扩展知识:JdbcDaoSupport 

这个类可以根据连接池创建JdbcTemplate对象。所以在配置文件中需要将连接池注入到UserDaoImpl中,就不需要配置JdbcTemplate了。

修改后的UserDaoImpl.java

public class UserDaoImpl extends JdbcDaoSupport implements UserDao { //这个父类JdbcDaoSupport会根据连接池帮你创建JdbcTemplate对象,这样就不需要在配置文件中配置JdbcTemplate
    // 子类在使用时只需要调用父类的getJdbcTemplate()方法获取JdbcTemplate对象
//public class UserDaoImpl implements UserDao {

    //private JdbcTemplate jt;

    @Override
    public void save(User u) {
        String sql = "insert into t_user values(null,?) ";
        //jt.update(sql, u.getName());
        super.getJdbcTemplate().update(sql, u.getName());
    }

    @Override
    public void delete(Integer id) {
        String sql = "delete from t_user where id = ? ";
        //jt.update(sql, id);
        super.getJdbcTemplate().update(sql,id);
    }

    @Override
    public void update(User u) {
        String sql = "update  t_user set name = ? where id=? ";
        //jt.update(sql, u.getName(), u.getId());
        super.getJdbcTemplate().update(sql, u.getName(),u.getId());
    }

    @Override
    public User getById(Integer id) {
        String sql = "select * from t_user where id = ? ";
        /*return jt.queryForObject(sql, new RowMapper<User>() {
            @Override
            public User mapRow(ResultSet resultSet, int i) throws SQLException { //Spring会帮忙遍历ResultSet,不用判断,能进入这个方法,一定有值
                User u = new User();
                u.setId(resultSet.getInt("id"));
                u.setName(resultSet.getString("name"));

                return u;
            }
        }, id);*/
        return super.getJdbcTemplate().queryForObject(sql,new RowMapper<User>(){
            @Override
            public User mapRow(ResultSet rs, int arg1) throws SQLException {
                User u = new User();
                u.setId(rs.getInt("id"));
                u.setName(rs.getString("name"));
                return u;
            }}, id);
    }

    @Override
    public int getTotalCount() {
        String sql = "select count(*) from t_user  ";
        //Integer count = jt.queryForObject(sql, Integer.class);
        Integer count = super.getJdbcTemplate().queryForObject(sql, Integer.class);

        return count;
    }

    @Override
    public List<User> getAll() {
        String sql = "select * from t_user  ";

        /*List<User> list = jt.query(sql, new RowMapper<User>() {
            @Override
            public User mapRow(ResultSet resultSet, int i) throws SQLException {
                User u = new User();
                u.setId(resultSet.getInt("id"));
                u.setName(resultSet.getString("name"));

                return u;
            }
        });*/

        List<User> list = super.getJdbcTemplate().query(sql, new RowMapper<User>(){
            @Override
            public User mapRow(ResultSet rs, int arg1) throws SQLException {
                User u = new User();
                u.setId(rs.getInt("id"));
                u.setName(rs.getString("name"));
                return u;
            }
        });

        return list;
    }

//    public void setJt(JdbcTemplate jt) {
//        this.jt = jt;
//    }
}

修改后的applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context-4.2.xsd ">

    <!-- 1.将连接池放入spring容器.
        这个连接池com.mchange.v2.c3p0.ComboPooledDataSource以前是我们自己手动new出来,现在是Spring帮我们new出来 -->
    <bean name="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" >
        <property name="jdbcUrl" value="jdbc:mysql:///spring" ></property>
        <property name="driverClass" value="com.mysql.jdbc.Driver" ></property>
        <property name="user" value="root" ></property>
        <property name="password" value="123456" ></property>
    </bean>


    <!-- 2.将JDBCTemplate放入spring容器 -->
    <!--<bean name="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate" >-->
        <!--<property name="dataSource" ref="dataSource" ></property> <!–set方式注入连接池–>-->
    <!--</bean>-->

    <!-- 3.将UserDao放入spring容器 -->
    <bean name="userDao" class="com.wisedu.jdbctemplate.UserDaoImpl" >
        <!--<property name="jt" ref="jdbcTemplate" ></property>-->
        <property name="dataSource" ref="dataSource" ></property>
    </bean>

</beans>  

测试代码内容不变,可以再次测试下。

 

6. db.properties

在实际开发中,数据库连接信息可能会变,每次都要打开Spring的配置文件applicationContext.xml文件进行修改,所以把数据库的信息挪到外面的一个文件中,比如在src下新建一个文件db.properties

jdbc.jdbcUrl=jdbc:mysql:///spring
jdbc.driverClass=com.mysql.jdbc.Driver
jdbc.user=root
jdbc.password=123456

【注意】:properties里的key建议加个前缀,防止某个key与Spring中的某些关键词重复,那么这个key就读不出来了。尤其user这个键。

那么applicationContext.xml文件关于连接池的配置修改如下:

    <!-- 指定spring读取db.properties配置
        property-placeholder用来指定读取properties配置文件 -->
    <context:property-placeholder location="classpath:db.properties" />

    <!-- 1.将连接池放入spring容器.
        这个连接池com.mchange.v2.c3p0.ComboPooledDataSource以前是我们自己手动new出来,现在是Spring帮我们new出来 -->
    <bean name="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" >
        <property name="jdbcUrl" value="${jdbc.jdbcUrl}" ></property>
        <property name="driverClass" value="${jdbc.driverClass}" ></property>
        <property name="user" value="${jdbc.user}" ></property>
        <property name="password" value="${jdbc.password}" ></property>
    </bean>

 

二、Spring中的AOP管理事务

1. 事务回顾

(1)什么是事务

事务是逻辑上的一组操作,组成这组操作的各个逻辑单元,要么一起成功,要么一起失败。

(2)事务特性(ACID)

  • 原子性:强调事务的不可分割。
  • 一致性:事务的执行的前后数据的完整性保持一致。
  • 隔离性:一个事务执行的过程中,不应该受到其他事务的干扰
  • 持久性:事务一旦结束,数据就持久到数据库

(3)如果不考虑隔离性引发安全性问题(并发问题)

  • 脏读:一个事务读到了另一个事务的未提交的数据
  • 不可重复读:一个事务读到了另一个事务已经提交的 update 的数据导致多次查询结果不一致.
  • 幻读:一个事务读到了另一个事务已经提交的 insert 的数据导致多次查询结果不一致.

(4)解决读问题:设置事务隔离级别(解决并发问题)

  • 读未提交:脏读,不可重复读,虚读都有可能发生
  • 读已提交:避免脏读。但是不可重复读和虚读有可能发生
  • 可重复读:避免脏读和不可重复读.但是虚读有可能发生.
  • 串行化:避免以上所有读问题.

Mysql 默认:可重复读

Oracle 默认:读已提交

 

2. Spring中事务

Spring封装了事务管理代码,无非就是打开事务的代码,提交事务的代码以及回滚事务的代码。因为使用不同平台(JDBC、Hibernate、Mybatis),操作事务的代码各不相同,所以Spring提供了一个接口PlatformTransactionManager,平台事务管理器。这个接口中声明了事务操作的方法,针对不同的平台,Spring提供不同的实现类。比如针对JDBC平台,提供的实现类是DataSourceTransactionManager,针对Hibernate平台提供的实现类是HibernateTransactionManager。

【注意】:在Spring中玩事务管理,最核心的对象就是 TransactionManager 对象。

 

3. Spring管理事务的属性介绍

事务封装好了,可以通过属性来配置事务。

  • 事务的隔离级别  isolation
  • 事务是否只读  read-only
  • 事务的传播行为 propagation

技术分享图片

PROPAGION_XXX:事务的传播行为。

保证同一个事务中:

  • PROPAGATION_REQUIRED 支持当前事务,如果不存在,就新建一个(默认)  --99.999的情况都是用这种
  • PROPAGATION_SUPPORTS 支持当前事务,如果不存在,就不使用事务
  • PROPAGATION_MANDATORY 支持当前事务,如果不存在,抛出异常

保证没有在同一个事务中:

  • PROPAGATION_REQUIRES_NEW 如果有事务存在,挂起当前事务,创建一个新的事务
  • PROPAGATION_NOT_SUPPORTED 以非事务方式运行,如果有事务存在,挂起当前事务
  • PROPAGATION_NEVER 以非事务方式运行,如果有事务存在,抛出异常
  • PROPAGATION_NESTED 如果当前事务存在,则嵌套事务执行

 

4. Spring事务环境准备

业务环境:转账

表:

技术分享图片

表中数据:

技术分享图片

准备Dao和Service层:

技术分享图片

AccountDao.java

package com.wisedu.dao;

/**
 * Created by jkzhao on 12/20/17.
 */
public interface AccountDao {

    //加钱
    void increaseMoney(Integer id, Double money);

    //减钱
    void decreaseMoney(Integer id, Double money);

}

AccountDaoImpl.java

package com.wisedu.dao.impl;

import com.wisedu.dao.AccountDao;
import org.springframework.jdbc.core.support.JdbcDaoSupport;

/**
 * Created by jkzhao on 12/20/17.
 */
public class AccountDaoImpl extends JdbcDaoSupport implements AccountDao {

    @Override
    public void increaseMoney(Integer id, Double money) {
        getJdbcTemplate().update("update t_account set money = money+? where id = ?",money, id);
    }

    @Override
    public void decreaseMoney(Integer id, Double money) {

        getJdbcTemplate().update("update t_account set money = money-? where id = ?",money, id);
    }
}

AccountService.java

package com.wisedu.service;

/**
 * Created by jkzhao on 12/20/17.
 */
public interface AccountService {
    //转账
    void transfer(Integer from, Integer to, Double money);

}

AccountServiceImpl.java

ackage com.wisedu.service.impl;

import com.wisedu.dao.AccountDao;
import com.wisedu.service.AccountService;

/**
 * Created by jkzhao on 12/20/17.
 */
public class AccountServiceImpl implements AccountService {

    private AccountDao ad;

    @Override
    public void transfer(Integer from, Integer to, Double money) {
        //减钱
        ad.decreaseMoney(from, money);
        //加钱
        ad.increaseMoney(to, money);

    }

    public void setAd(AccountDao ad) {
        this.ad = ad;
    }
}

配置文件applicationContext.xml:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context-4.2.xsd ">

    <!-- 指定spring读取db.properties配置
        property-placeholder用来指定读取properties配置文件 -->
    <context:property-placeholder location="classpath:db.properties" />

    <!-- 1.将连接池放入spring容器.
        这个连接池com.mchange.v2.c3p0.ComboPooledDataSource以前是我们自己手动new出来,现在是Spring帮我们new出来 -->
    <bean name="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" >
        <property name="jdbcUrl" value="${jdbc.jdbcUrl}" ></property>
        <property name="driverClass" value="${jdbc.driverClass}" ></property>
        <property name="user" value="${jdbc.user}" ></property>
        <property name="password" value="${jdbc.password}" ></property>
    </bean>

    <!-- 2.将AccountDao放入spring容器 -->
    <bean name="accountDao" class="com.wisedu.dao.impl.AccountDaoImpl" >
        <property name="dataSource" ref="dataSource" ></property>
    </bean>

    <!-- 3.将AccountDao放入spring容器 -->
    <bean name="accountService" class="com.wisedu.service.impl.AccountServiceImpl" >
        <property name="ad" ref="accountDao" ></property>
    </bean>

</beans>

编写测试代码,建一个包com.wisedu.tx,编写测试文件Demo.java

package com.wisedu.tx;

import com.wisedu.service.AccountService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import javax.annotation.Resource;

/**
 * Created by jkzhao on 12/20/17.
 */
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class Demo {

    @Resource(name = "accountService")
    private AccountService as;

    @Test
    public void fun1(){
        as.transfer(1, 2, 100d); //100d,因为该字段是double类型的
    }

}

执行方法fun1,查看数据库。

技术分享图片

【注意】:现在上面的代码中还没有添加事务。如果在转账的过程中出现了异常,比如修改AccountServiceImpl.java的部分代码:

public class AccountServiceImpl implements AccountService {

    private AccountDao ad;

    @Override
    public void transfer(Integer from, Integer to, Double money) {
        //减钱
        ad.decreaseMoney(from, money);
        
        int i = 1/0;
        
        //加钱
        ad.increaseMoney(to, money);

    }
    ...
}

 再次执行fun1方法,查看数据库。发现张三的钱少了,但是李四的钱并没有加上来。所以需要给这个Service加上事务。

技术分享图片

 

5. Spring管理事务方式(三种)

  • 编码式  --了解
  • xml配置式(属于aop) --重要
  • 注解配置(属于aop) --重要

 (1)编码式

在代码中管理事务,如果有10个方法用到事务,得在10个地方添加事务代码。

将核心事务管理器配置到Spring容器。在上面的applicationContext.xml文件中加入如下代码:

    <!-- 将核心事务管理器配置到Spring容器,封装了所有事务操作
            事务依赖DataSource -->
    <bean name="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"></property>
    </bean>

    <!-- 编码式事务 操作事务需要使用一个事务模板对象。把调用事务的操作封装到TransactionTemplate对象中,它依赖核心事务管理器。
            这个TransactionTemplate只是帮你去调事务处理的方法,但是方法是写在和事务管理器中的 -->
    <bean name="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
        <property name="transactionManager" ref="transactionManager"></property>
    </bean>

修改AccountServiceImpl.java的代码

package com.wisedu.service.impl;

import com.wisedu.dao.AccountDao;
import com.wisedu.service.AccountService;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionTemplate;

/**
 * Created by jkzhao on 12/20/17.
 */
public class AccountServiceImpl implements AccountService {

    private AccountDao ad;
    private TransactionTemplate tt;

    @Override
    public void transfer(final Integer from, final Integer to, final Double money) {
        tt.execute(new TransactionCallbackWithoutResult() {
            @Override
            protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
                //减钱
                ad.decreaseMoney(from, money);

                int i = 1/0;

                //加钱
                ad.increaseMoney(to, money);
            }
        });

    }

    public void setAd(AccountDao ad) {
        this.ad = ad;
    }

    public void setTt(TransactionTemplate tt) {
        this.tt = tt;
    }
}

在applicationContext.xml中的accountService这个bean里注入属性tt:

    <bean name="accountService" class="com.wisedu.service.impl.AccountServiceImpl" >
        <property name="ad" ref="accountDao" ></property>
        <property name="tt" ref="transactionTemplate"></property>
    </bean>

再次执行方法fun1,查看数据库结果。很显然,当遇到异常时,事务进行了回滚。

技术分享图片

但是很显然这种不合理,如果Service中有多个方法要用到事务,那么tt.excute()在每个方法里都得调用。

 

(2)xml配置aop事务

技术分享图片

Spring已经写好了一个事务的通知,如果Spring没有写这个通知,我们自己来实现的话,得用环绕通知。既然已经帮我们实现了通知,我们只需要将这个通知织入到目标对象上(在本案例中,目标对象是那个service),我们只需要配置就可以了。

a. 首先要导包:4 + 2 + aop + aspect + aop联盟包(com.springsource.org.aopalliance-1.0.0.jar)+ weaving织入包(com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar)

b. 导入新的命名空间aop和tx:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns="http://www.springframework.org/schema/beans"
       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-4.2.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context-4.2.xsd
       http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
        http://www.springframework.org/schema/aop
       http://www.springframework.org/schema/aop/spring-aop-4.2.xsd">
...

命名空间在本案例中的作用:

  • beans:最基本
  • context:注解和读取properties配置文件
  • aop:配置AOP(配置将事务通知织入目标对象)
  • tx:配置事务通知

c. 配置通知和织入

<!-- 将核心事务管理器配置到Spring容器,封装了所有事务操作
            事务依赖DataSource -->
    <bean name="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"></property>
    </bean>

    <!-- 方式一:编码式事务 操作事务需要使用一个事务模板对象。把调用事务的操作封装到TransactionTemplate对象中,它依赖核心事务管理器。
            这个TransactionTemplate只是帮你去调事务处理的方法,但是方法是写在和事务管理器中的 -->
    <!--<bean name="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">-->
        <!--<property name="transactionManager" ref="transactionManager"></property>-->
    <!--</bean>-->

    <!-- 方式二:xml方式管理事务 -->
    <!-- 配置事务通知
        事务通知是Spring已经写好了。我们只需要配置其事务管理器-->
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <tx:attributes> <!-- <tx:attributes> 来指定属性-->
            <!-- 以方法为粒度配置事务属性,有3个属性(isolation、propagation:传播行为、read-only)可以配置。每个属性有多个值可以选择 -->
            <tx:method name="transfer" isolation="REPEATABLE_READ" propagation="REQUIRED" read-only="false"/> <!--service里的这个transfer方法里面是要修改数据库的,所以read-only这个属性的值千万别配置true-->
            <!-- 企业当中开发以通配符来完成批量配置 两套增删改查 -->
            <tx:method name="save*" isolation="REPEATABLE_READ" propagation="REQUIRED" read-only="false"/>
            <tx:method name="persist*" isolation="REPEATABLE_READ" propagation="REQUIRED" read-only="false"/>
            <tx:method name="update*" isolation="REPEATABLE_READ" propagation="REQUIRED" read-only="false"/>
            <tx:method name="modify*" isolation="REPEATABLE_READ" propagation="REQUIRED" read-only="false"/>
            <tx:method name="delete*" isolation="REPEATABLE_READ" propagation="REQUIRED" read-only="false"/>
            <tx:method name="remove*" isolation="REPEATABLE_READ" propagation="REQUIRED" read-only="false"/>
            <tx:method name="get*" isolation="REPEATABLE_READ" propagation="REQUIRED" read-only="true"/>
            <tx:method name="find*" isolation="REPEATABLE_READ" propagation="REQUIRED" read-only="true"/>
        </tx:attributes>
    </tx:advice>

    <!-- 配置将事务通知织入目标对象 -->
    <aop:config>
        <!-- 配置切点表达式 -->
        <aop:pointcut id="txPc" expression="execution(* com.wisedu.service.impl.*ServiceImpl.*(..) )" />
        <!-- 配置切面
            advice-ref:通知的名称
            pointcut-ref:切点名称 -->
        <aop:advisor advice-ref="txAdvice" pointcut-ref="txPc"/> <!-- advisor:切面=通知+切入点 -->
    </aop:config>

将方式一的配置全部注释掉,执行Demo.java中的测试方法fun1,测试下无异常和有异常两种情况,查看数据库结果。

 

(3)注解配置aop事务

 导包和导入新的约束和上面的一样。

开启注解管理事务:

    <!-- 将核心事务管理器配置到Spring容器,封装了所有事务操作
            事务依赖DataSource -->
    <bean name="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"></property>
    </bean>

    <!-- 方式三:开启使用注解管理aop事务 -->
    <tx:annotation-driven transaction-manager="transactionManager" />

使用注解:

public class AccountServiceImpl implements AccountService {

    private AccountDao ad;
    private TransactionTemplate tt;

    @Override
    @Transactional(isolation = Isolation.REPEATABLE_READ, propagation = Propagation.REQUIRED, readOnly = false)
    public void transfer(Integer from, Integer to, Double money) {

        //减钱
        ad.decreaseMoney(from, money);

        //int i = 1/0;

        //加钱
        ad.increaseMoney(to, money);

    }
...

将方式一和方式二的配置全部注释掉,执行Demo.java中的测试方法fun1,测试下无异常和有异常两种情况,查看数据库结果。

但是注解这种方式,每个方法上都要加注解。我们可以把这个注解加到类上。

@Transactional(isolation = Isolation.REPEATABLE_READ, propagation = Propagation.REQUIRED, readOnly = false)
public class AccountServiceImpl implements AccountService {

    private AccountDao ad;
    private TransactionTemplate tt;

    @Override
    public void transfer(Integer from, Integer to, Double money) {

        //减钱
        ad.decreaseMoney(from, money);

        int i = 1/0;

        //加钱
        ad.increaseMoney(to, money);

    }
...

这样类中的全部方法都会使用这个注解,如果某个方法需要使用注解时的属性值不一样,可以在方法上单独写一个注解。

 

以上是关于Spring整合JDBC以及AOP管理事务的主要内容,如果未能解决你的问题,请参考以下文章

Spring整合JDBC以及AOP管理事务

JAVAEE——spring03:spring整合JDBC和aop事务

Spring与JDBC的整合使用

SSH框架的基本整合

spring-jdbc-aop事务

SSH框架的基本整合