zoukankan      html  css  js  c++  java
  • 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 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);
    		
    	}
    	
    }

    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);
    
        }
    ...

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

  • 相关阅读:
    8u111-jdk-alpine 字体缺少FontConfiguration的NullPointerException错误解决方案
    Mybatis插件原理
    Mybaits 分页
    @requestBody 和@RequestParam
    Mybaits 生产mapper
    powerDesigner 生成SQL时没有注释问题
    HashMap 的 put过程
    Java的锁
    Java1.8 JVM配置 GC日志输出
    Windows 安装两个MYSQL实例
  • 原文地址:https://www.cnblogs.com/zhaojiankai/p/8370553.html
Copyright © 2011-2022 走看看