Spring核心之事务管理

JdbcTemplate

了解API

POJO类

public class User {

    private Integer id;
    private String username;
    private String password;

    //getters、setters、toString
}

测试类

public class TestApi {

    public static void main(String[] args) {
        //1、创建数据源(连接池) dbcp
        BasicDataSource dataSource = new BasicDataSource();
        // 配置基本4项
        dataSource.setDriverClassName("com.mysql.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql://localhost:3306/test");
        dataSource.setUsername("root");
        dataSource.setPassword("123");


        //2、创建模板
        JdbcTemplate jdbcTemplate = new JdbcTemplate();
        jdbcTemplate.setDataSource(dataSource);


        //3、通过api操作
        String sql = "insert into t_user(username,password) values(?,?)";
        String[] sqlArgs = {"tyshawn", "123456"};
        jdbcTemplate.update(sql, sqlArgs);

    }
}

配置DBCP

UserDao

public class UserDao {

    //jdbc模板将由spring注入
    private JdbcTemplate jdbcTemplate;
    public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }

    public void update(User user){
        String sql = "update t_user set username=?,password=? where id =?";
        Object[] args = {user.getUsername(), user.getPassword(), user.getId()};
        jdbcTemplate.update(sql, args);
    }
}

beans.xml

<beans>
    <!-- 创建数据源 -->
    <bean id="dataSourceId" class="org.apache.commons.dbcp.BasicDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
        <property name="url" value="jdbc:mysql://localhost:3306/test"></property>
        <property name="username" value="root"></property>
        <property name="password" value="123"></property>
    </bean>

    <!-- 创建模板 ,需要注入数据源-->
    <bean id="jdbcTemplateId" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSourceId"></property>
    </bean>

    <!-- 配置dao -->
    <bean id="userDaoId" class="org.dbcp.UserDao">
        <property name="jdbcTemplate" ref="jdbcTemplateId"></property>
    </bean>
</beans>

测试代码

@Test
public void demo(){
    User user = new User();
    user.setId(1);
    user.setUsername("alice");
    user.setPassword("321");

    String xmlPath = "org/dbcp/beans.xml";
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext(xmlPath);

    //获得目标类
    UserDao userDao = (UserDao) applicationContext.getBean("userDaoId");
    userDao.update(user);
}

配置C3P0

UserDao

public class UserDao {
    private JdbcTemplate jdbcTemplate;

    public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }

    public List<User> findAll() {
        return jdbcTemplate.query("select * from t_user", ParameterizedBeanPropertyRowMapper.newInstance(User.class));
    }
}

beans.xml

<beans>
    <!-- 创建数据源 -->
    <bean id="dataSourceId" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
        <property name="url" value="jdbc:mysql://localhost:3306/test"></property>
        <property name="username" value="root"></property>
        <property name="password" value="123"></property>
    </bean>

    <!-- 创建模板,需要注入数据源-->
    <bean id="jdbcTemplateId" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSourceId"></property>
    </bean>

    <!-- 配置dao -->
    <bean id="userDaoId" class="org.c3p0.UserDao">
        <property name="jdbcTemplate" ref="jdbcTemplateId"></property>
    </bean>
</beans>

测试代码

@Test
public void demo(){

    String xmlPath = "org/c3p0/beans.xml";
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext(xmlPath);

    //获得目标类
    UserDao userDao = (UserDao) applicationContext.getBean("userDaoId");
    List<User> allUser = userDao.findAll();

    System.out.println(allUser);
}

使用JdbcDaoSupport

JdbcDaoSupport接口只需要注入数据源,底层将自动创建JdbcTemplate。

UserDao

public class UserDao extends JdbcDaoSupport{

    public List<User> findAll() {
        return this.getJdbcTemplate().query("select * from t_user", ParameterizedBeanPropertyRowMapper.newInstance(User.class));
    }
}

beans.xml

<beans>
    <!-- 创建数据源 -->
    <bean id="dataSourceId" class="org.apache.commons.dbcp.BasicDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
        <property name="url" value="jdbc:mysql://localhost:3306/test"></property>
        <property name="username" value="root"></property>
        <property name="password" value="123"></property>
    </bean>

    <!-- 
        * 配置dao 
        * dao 继承 JdbcDaoSupport,之后只需要注入数据源,底层将自动创建模板
    -->
    <bean id="userDaoId" class="org.jdbcdaosupport.UserDao">
        <property name="dataSource" ref="dataSourceId"></property>
    </bean>
</beans>

测试代码

@Test
public void demo(){

    String xmlPath = "org/jdbcdaosupport/beans.xml";
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext(xmlPath);

    //获得目标类
    UserDao userDao = (UserDao) applicationContext.getBean("userDaoId");
    List<User> allUser = userDao.findAll();

    System.out.println(allUser);
}

配置properties

properties文件


beans.xml

<beans>
    <!-- 
        * 加载配置文件 
        * "classpath:"前缀表示 src下
        * 在配置文件之后通过  ${key} 获得内容
    -->
    <context:property-placeholder location="classpath:jdbcInfo.properties"/>

    <!-- 创建数据源-->
    <bean id="dataSourceId" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="driverClass" value="${jdbc.driverClass}"></property>
        <property name="jdbcUrl" value="${jdbc.jdbcUrl}"></property>
        <property name="user" value="${jdbc.user}"></property>
        <property name="password"  value="${jdbc.password}"></property>
    </bean>
</beans>

Spring事务管理

事务

指作为单个逻辑工作单元执行的一系列操作,要么完全地执行,要么完全地不执行。事务的存在主要是为了保障业务数据的完整性和准确性。

事务的四大特性

(1)原子性(Atomicity)
指事务包含的所有操作要么全部成功,要么全部失败回滚。

(2)一致性(Consistency)
一致性是指事务必须使数据库从一个一致性状态变换到另一个一致性状态,也就是说一个事务执行之前和执行之后都必须处于一致性状态。

拿转账来说,假设用户A和用户B两者的钱加起来一共是5000,那么不管A和B之间如何转账,转几次账,事务结束后两个用户的钱相加起来应该还得是5000,这就是事务的一致性。

(3)隔离性(Isolation)
指并发的事务是相互隔离的。当多个用户并发访问数据库时,比如操作同一张表时,数据库为每一个用户开启的事务,不能被其他事务的操作所干扰,多个并发事务之间要相互隔离。

(4)持久性(Durability)
指一个事务一旦被提交了,那么对数据库中的数据的改变就是永久性的,即便是在数据库系统遇到故障的情况下也不会丢失提交事务的操作。

隔离问题

(1)脏读

指在一个事务处理过程中读取了另一个未提交的事务中的数据。如果事务T1读到了事务T2未提交的数据,如果T2提交失败,那么T1也要回滚(级联废弃)。

(2)不可重复读

指对于数据库中的某个数据,一个事务范围内多次查询却返回了不同的数据值,这是由于在查询间隔,被另一个事务修改并提交了。不可重复读和脏读的区别是,脏读是某一事务读取了另一个事务未提交的脏数据,而不可重复读则是读取了前一事务提交的数据。

(3)虚读(幻读)

一事务对数据进行了新增操作,另一事务两次查询的数据不一致。例如事务T1对一个表中所有的行的某个数据项做了从“1”修改为“2”的操作,这时事务T2又对这个表中插入了一行数据项,而这个数据项的数值还是为“1”并且提交给数据库。而操作事务T1的用户如果再查看刚刚修改的数据,会发现还有一行没有修改,其实这行是从事务T2中添加的,就好像产生幻觉一样,这就是发生了幻读。

幻读和不可重复读都是读取了另一条已经提交的事务(这点就脏读不同),所不同的是不可重复读查询的都是同一个数据项,而幻读针对的是一批数据整体(比如行数的变化)。

隔离级别

  • Read Uncommitted (读未提交):最低级别,任何情况都无法保证。
  • Read Committed (读已提交):可避免脏读的发生。
  • Repeatable Read (可重复读):可避免脏读、不可重复读的发生(MySQL默认隔离级别)。
  • Serializable (串行化):可避免脏读、不可重复读、幻读的发生。

MySQL事务操作

(1)简单情况:一个事务包括ABCD四个操作。

Connection conn = null;
try{
  //1、获得连接
  conn = ...;
  //2、开启事务
  conn.setAutoCommit(false);
  A
  B
  C
  D
  //3、提交事务
  conn.commit();
} catche(){
  //4、回滚事务
  conn.rollback();
}

(2)Savepoint:一个事务包括ABCD四个操作,AB必须执行,CD可选择执行。

Connection conn = null;
Savepoint savepoint = null;  //保存点,记录操作的当前位置,之后可以回滚到指定的位置。
try{
  //1、获得连接
  conn = ...;
  //2、开启事务
  conn.setAutoCommit(false);
  A
  B
  savepoint = conn.setSavepoint();
  C
  D
  //3、提交事务
  conn.commit();
} catche(){
  if(savepoint != null){   //CD异常
     // 回滚到CD之前
     conn.rollback(savepoint);
     // 提交AB
     conn.commit();
  } else{   //AB异常
     // 回滚AB
     conn.rollback();
  }
}

两个重要的事务属性

(1)Isolation(隔离性)

  • DEFAULT: 使用数据库设置
  • READ_UNCOMMITTED: 会出现脏读、不可重复读、幻读
  • READ_COMMITTED: 会出现不可重复读、幻读问题
  • REPEATABLE_READ: 会出现幻读
  • SERIALIZABLE: 保证所有的情况不会发生

(2)Propagation (传播性):在两个业务之间如何共享事务。

  • REQUIRED
    • 支持当前事务,A如果有事务,B将使用该事务。如果A没有事务,B将创建一个新的事务。
  • SUPPORTS
    • 支持当前事务,A如果有事务,B将使用该事务。如果A没有事务,B将以非事务执行。
  • REQUIRES_NEW
    • 如果A有事务,将A的事务挂起,B创建一个新的事务。如果A没有事务,B创建一个新的事务。
  • NOT_SUPPORTED
    • 如果A有事务,将A的事务挂起,B将以非事务执行。如果A没有事务,B将以非事务执行。
  • MANDATORY
    • 支持当前事务,A如果有事务,B将使用该事务。如果A没有事务,B将抛异常。
  • NEVER
    • 如果A有事务,B将抛异常。如果A没有事务,B将以非事务执行。
  • NESTED
    • A和B底层采用保存点机制,形成嵌套事务。

事务管理案例:转账

(1)dao层

public interface AccountDao {
     //汇款
    public void out(String outer , Integer money);

    //收款
    public void in(String inner , Integer money);
}

public class AccountDaoImpl extends JdbcDaoSupport implements AccountDao {

    public void out(String outer, Integer money) {
        this.getJdbcTemplate().update("update account set money = money - ? where username = ?", money,outer);
    }

    public void in(String inner, Integer money) {
        this.getJdbcTemplate().update("update account set money = money + ? where username = ?", money,inner);
    }
}

(2)Service层

public interface AccountService {
    //转账
    public void transfer(String outer ,String inner ,Integer money);
}

public class AccountServiceImpl implements AccountService {

    private AccountDao accountDao;
    public void setAccountDao(AccountDao accountDao) {
        this.accountDao = accountDao;
    }

    public void transfer(String outer, String inner, Integer money) {
        accountDao.out(outer, money);
        //模拟故障
        int i = 1/0;
        accountDao.in(inner, money);
    }
}

(3)Spring配置

<beans>
    <!-- 1、datasource -->
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="driverClass" value="com.mysql.jdbc.Driver"></property>
        <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/test"></property>
        <property name="user" value="root"></property>
        <property name="password" value="123"></property>
    </bean>

    <!-- 2、dao  -->
    <bean id="accountDao" class="org.tx.dao.impl.AccountDaoImpl">
        <property name="dataSource" ref="dataSource"></property>
    </bean>

    <!-- 3、service -->
    <bean id="accountService" class="org.tx.service.impl.AccountServiceImpl">
        <property name="accountDao" ref="accountDao"></property>
    </bean>
</beans>

(4)测试代码

@Test
public void demo(){
    String xmlPath = "applicationContext.xml";
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext(xmlPath);
    AccountService accountService =  (AccountService) applicationContext.getBean("accountService");
    accountService.transfer("jack", "rose", 1000);
}

(5)结果

手动管理事务

Spring底层使用 TransactionTemplate 事务模板进行操作。

(1)修改Service

public class AccountServiceImpl implements AccountService {

    private AccountDao accountDao;
    public void setAccountDao(AccountDao accountDao) {
        this.accountDao = accountDao;
    }

    private TransactionTemplate transactionTemplate;
    public void setTransactionTemplate(TransactionTemplate transactionTemplate) {
        this.transactionTemplate = transactionTemplate;
    }

    public void transfer(final String outer, final String inner, final Integer money) {
        transactionTemplate.execute(new TransactionCallbackWithoutResult() {

            @Override
            protected void doInTransactionWithoutResult(TransactionStatus arg0) {
                accountDao.out(outer, money);
                //模拟故障
                int i = 1/0;
                accountDao.in(inner, money);
            }
        });
    }
}

(2)修改Spring配置

<beans>
    <!-- 1、datasource -->
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="driverClass" value="com.mysql.jdbc.Driver"></property>
        <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/test"></property>
        <property name="user" value="root"></property>
        <property name="password" value="123"></property>
    </bean>

    <!-- 2、dao  -->
    <bean id="accountDao" class="org.tx.dao.impl.AccountDaoImpl">
        <property name="dataSource" ref="dataSource"></property>
    </bean>

    <!-- 3、service -->
    <bean id="accountService" class="org.tx.service.impl.AccountServiceImpl">
        <property name="accountDao" ref="accountDao"></property>
        <property name="transactionTemplate" ref="transactionTemplate"></property>
    </bean>

    <!-- 4、创建事务模板 -->
    <bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
        <property name="transactionManager" ref="txManager"></property>
    </bean>

    <!-- 5、配置事务管理器 ,管理器需要事务,事务从Connection获得,连接从连接池DataSource获得 -->
    <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"></property>
    </bean>
</beans>

工厂 bean 生成代理:半自动

(1)Service层

public interface AccountService {
    //转账
    public void transfer(String outer ,String inner ,Integer money);
}

public class AccountServiceImpl implements AccountService {

    private AccountDao accountDao;
    public void setAccountDao(AccountDao accountDao) {
        this.accountDao = accountDao;
    }

    public void transfer(String outer, String inner, Integer money) {
        accountDao.out(outer, money);
        //模拟故障
        int i = 1/0;
        accountDao.in(inner, money);
    }
}

(2)Spring配置文件

<beans>
    <!-- 1、datasource -->
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="driverClass" value="com.mysql.jdbc.Driver"></property>
        <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/test"></property>
        <property name="user" value="root"></property>
        <property name="password" value="123"></property>
    </bean>

    <!-- 2、dao  -->
    <bean id="accountDao" class="org.tx.dao.impl.AccountDaoImpl">
        <property name="dataSource" ref="dataSource"></property>
    </bean>

    <!-- 3、service -->
    <bean id="accountService" class="org.tx.service.impl.AccountServiceImpl">
        <property name="accountDao" ref="accountDao"></property>
    </bean>

    <!-- 4、service 代理对象 
        4.1 proxyInterfaces 接口 
        4.2 target 目标类
        4.3 transactionManager 事务管理器
        4.4 transactionAttributes 事务属性(事务详情)
            prop.key :确定哪些方法使用当前事务配置
            prop.text:用于配置事务详情
                格式:PROPAGATION,ISOLATION,readOnly,-Exception,+Exception
                    传播行为        隔离级别        是否只读        异常回滚        异常提交
                例如:
                    <prop key="transfer">PROPAGATION_REQUIRED,ISOLATION_DEFAULT</prop> 默认传播行为,和隔离级别
                    <prop key="transfer">PROPAGATION_REQUIRED,ISOLATION_DEFAULT,readOnly</prop> 只读
                    <prop key="transfer">PROPAGATION_REQUIRED,ISOLATION_DEFAULT,+java.lang.ArithmeticException</prop>  有异常扔提交
    -->
    <bean id="proxyAccountService" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
        <property name="proxyInterfaces" value="org.tx.service.AccountService"></property>
        <property name="target" ref="accountService"></property>
        <property name="transactionManager" ref="txManager"></property>
        <property name="transactionAttributes">
            <props>
                <prop key="transfer">PROPAGATION_REQUIRED,ISOLATION_DEFAULT</prop>
            </props>
        </property>
    </bean>

    <!-- 5、配置事务管理器  -->
    <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"></property>
    </bean>
</beans>

(3)测试代码

@Test
public void demo(){
    String xmlPath = "applicationContext.xml";
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext(xmlPath);
    AccountService accountService =  (AccountService) applicationContext.getBean("proxyAccountService");
    accountService.transfer("jack", "rose", 1000);
}

AOP配置基于xml

(1)Service层

public interface AccountService {
    //转账
    public void transfer(String outer ,String inner ,Integer money);
}

public class AccountServiceImpl implements AccountService {

    private AccountDao accountDao;
    public void setAccountDao(AccountDao accountDao) {
        this.accountDao = accountDao;
    }

    public void transfer(String outer, String inner, Integer money) {
        accountDao.out(outer, money);
        //模拟故障
        int i = 1/0;
        accountDao.in(inner, money);
    }
}

(2)Spring配置文件

<beans>
    <!-- 1、datasource -->
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="driverClass" value="com.mysql.jdbc.Driver"></property>
        <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/test"></property>
        <property name="user" value="root"></property>
        <property name="password" value="123"></property>
    </bean>

    <!-- 2、dao  -->
    <bean id="accountDao" class="org.tx.dao.impl.AccountDaoImpl">
        <property name="dataSource" ref="dataSource"></property>
    </bean>

    <!-- 3、service -->
    <bean id="accountService" class="org.tx.service.impl.AccountServiceImpl">
        <property name="accountDao" ref="accountDao"></property>
    </bean>

    <!-- 4、事务管理器 -->
    <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"></property>
    </bean>

    <!-- 5、事务通知 
        <tx:attributes> 用于配置事务详情(事务属性)  
    -->
    <tx:advice id="txAdvice" transaction-manager="txManager">
        <tx:attributes>
            <tx:method name="transfer" propagation="REQUIRED" isolation="DEFAULT"/>
        </tx:attributes>
    </tx:advice>

    <!-- 6、AOP编程 -->
    <aop:config>
        <aop:advisor advice-ref="txAdvice" pointcut="execution(* org.tx.service.*.*(..))"/>
    </aop:config>
</beans>

(3)测试代码

@Test
public void demo(){
    String xmlPath = "applicationContext.xml";
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext(xmlPath);
    AccountService accountService =  (AccountService) applicationContext.getBean("accountService");
    accountService.transfer("jack", "rose", 1000);
}

AOP配置基于注解

(1)Service层

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

    private AccountDao accountDao;
    public void setAccountDao(AccountDao accountDao) {
        this.accountDao = accountDao;
    }

    public void transfer(String outer, String inner, Integer money) {
        accountDao.out(outer, money);
        //模拟故障
        int i = 1/0;
        accountDao.in(inner, money);
    }
}

(2)Spring配置文件

<beans>
        <!-- 1、datasource -->
        <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
            <property name="driverClass" value="com.mysql.jdbc.Driver"></property>
            <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/test"></property>
            <property name="user" value="root"></property>
            <property name="password" value="123"></property>
        </bean>

        <!-- 2、dao  -->
        <bean id="accountDao" class="org.tx.dao.impl.AccountDaoImpl">
            <property name="dataSource" ref="dataSource"></property>
        </bean>

        <!-- 3、service -->
        <bean id="accountService" class="org.tx.service.impl.AccountServiceImpl">
            <property name="accountDao" ref="accountDao"></property>
        </bean>

        <!-- 4、事务管理器 -->
        <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
            <property name="dataSource" ref="dataSource"></property>
        </bean>

        <!-- 5、将事务管理器交予Spring -->
        <tx:annotation-driven transaction-manager="txManager"/>
</beans>

(3)测试代码

@Test
public void demo(){
    String xmlPath = "applicationContext.xml";
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext(xmlPath);
    AccountService accountService =  (AccountService) applicationContext.getBean("accountService");
    accountService.transfer("jack", "rose", 1000);
}

猜你喜欢

转载自blog.csdn.net/litianxiang_kaola/article/details/79371255