即日起在codingBlog上分享您的技术经验即可获得积分,积分可兑换现金哦。

Spring 06 声明式事务

编程语言 adsl624153 24℃ 0评论
本文目录
[隐藏]

1.1、事务的概念

将一组操作数据的SQL作为一个整体提交,要么都成功,要么都失败

使用的原因:


保证数据的完整性和一致性,对数据操作是要保证数据的安全

2.2、事务的特性【ACID】

⑴ 原子性


将多个SQL作为一个整体,不可分割

⑵ 一致性


数据在操作前是正确的,操作后也是正确的


操作的结果应该和业务逻辑保持一致

⑶ 隔离性


多个事务并发操作同一个数据时,保证事务间的数据是隔离开来的,相关不会受到干扰,保证数据的安全

⑷ 持久性


事务操作数据库的结果,应该永久地保存到持久化存储器中

3.3、事务的隔离级别

3.1.分类

3.1.1.未提交读

int TRANSACTION_READ_UNCOMMITTED = 1;


一个事务读取到了另一个事务还没有提交的数据

可能产生脏读,不可重复读,幻读问题。但是它解决了数据丢失问题

例如:

   T1正在读取数据,并对数据进行操作
   T2 修改了数据,但是还没有提交数据
   T1 再次读取数据,可能发生脏读问题。因为T2可能会回滚事务

3.1.2.已提交读

int TRANSACTION_READ_COMMITTED = 2;


一个事务读取到了另外一个事务已经提交过的数据

可能产生不可重复读,幻读问题。但是它解决了数据丢失和脏读问题

例如:

    T1 正在读取数据,并对数据进行操作
    T2 修改了数据,并提交了数据
    T1 再次读取数据,发现两次读取到的数据不一致。这就产生了不可重复读问题

3.1.3.重复读

int TRANSACTION_REPEATABLE_READ = 4;


一个事务只能重复的读取当前事务中的操作数据,而不能读取到另外的事务中未提交和已提交的数据

可能产生幻读。但是它解决了数据丢失,脏读和不可重复读问题

例如:

     T1 正在读取数据,并对数据进行操作
     T2 修改了数据,并且提交了数据,但是没有提交数据
     T1 再次读取数据,两次读取到的数据是一致的

3.1.4.序列化/不可并发

int TRANSACTION_SERIALIZABLE = 8;


可以解决所有的问题,一般配合数据库锁的机制控制数据的安全【统计工作】

  幻读问题:一个事务正在进行某种统计操作,其他事务又进行数据的增删改等操作,导致两次统计到的结果不一致

例如:

     T1 正在统计表的记录数,得到一个结果
     T2 向数据库中添加了一些数据
     T3 再次统计表的记录数,得到的结果就比T1统计到的多

Tips:

   SELECT * FROM ??? WHERE ??? FOR UPDATE; // 行级锁

3.2.数据库的默认隔离级别



MySQL 4(重复读)


Oracle 2(已提交读)

4.4、事务管理方式

4.1.编程式事务

即使用原生的JDBC API进行事务的管理

步骤:


⑴ 获取数据库连接对象【Connection】


⑵ 取消事务的自动提交


⑶ 执行SQL操作


⑷ 正常完成操作时,手动提交事务


⑸ 执行失败时,回滚事务


⑹ 关闭相关资源(释放连接等)

4.2.声明式事务

通过相关配置,给程序方法增加事务的操作

5.5、Spring的声明式事务

5.1.相关API

Spring框架提供了PlatformTransactionManager接口,用来管理底层的事务。并且提供了相关的实现类

⑴ DataSourceTransactionManager


Spring和JdbcTemplate或MyBatis框架集成时,提供的事务管理器

⑵ HibernateTransactionManager


Spring和Hibernate框架集成时,提供的事务管理器

5.2.使用步骤

⑴ 拷贝jar包


① 和IOC有关的:

   commons-logging-1.1.3.jar
   spring-beans-4.0.0.RELEASE.jar
   spring-context-4.0.0.RELEASE.jar
   spring-core-4.0.0.RELEASE.jar
   spring-expression-4.0.0.RELEASE.jar

② 和AOP有关的:

  spring-aop-4.0.0.RELEASE.jar
  spring-aspects-4.0.0.RELEASE.jar
  com.springsource.net.sf.cglib-2.2.0.jar
  com.springsource.org.aopalliance-1.0.0.jar
  com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar

③ 和数据库有关的:

   spring-jdbc-4.0.0.RELEASE.jar
   spring-orm-4.0.0.RELEASE.jar
   spring-tx-4.0.0.RELEASE.jar

④ 数据库驱动和c3p0

   mysql-connector-java-5.1.7-bin.jar
   c3p0-0.9.1.2.jar

⑵ 创建c3p0的properties配置文件


⑶ 创建核心配置文件


添加context和tx名称空间

① 通过

    

标签,来设置自动扫描包

② 通过

    

标签,来引入外部配置文件

③ 通过bean 标签,声明c3p0即DataSource对象

④ 通过bean 标签,声明JdbcTemplate对象。注意需要给其dataSource属性赋值,引用DataSource(c3p0)对象


    

⑤ 通过bean标签,声明TransactionManager对象。注意需要给其dataSource属性赋值,引用DataSource(c3p0)对象


    

以DataSourceTransactionManager为例

⑥ 通过

    

标签,来开启基于注解的声明式事务

⑷ 编写具体的DAO、Service,并在具体的业务方法上,加上@Transactional注解,即可给方法开启事务

6.6、Transactional【事务】的属性

6.1.事务传播行为

【propagation属性】


方法被调用时,事务的开启方式

一共有7个传播行为,常用的有2个:


⑴ Propagation.REQUIRED


表示一个方法被调用时,如果已经存在了一个事务,则加入其中;否则,会开启一个新的事务。【默认值】

⑵ Propagation.REQUIRES_NEW


表示一个方法被调用时,不管调用的方法是否存在事务,都会开启一个新的事务

6.2.事务隔离级别

【isolation属性】

Isolation.DEFAULT


表示与数据库的默认隔离级别一致


MySQL 4(重复读)

Isolation.READ_UNCOMMITTED 未提交读


Isolation.READ_COMMITTED 已提交读


Isolation.REPEATABLE_READ 重复读


Isolation.SERIALIZABLE 序列化/不可并发

6.3.事务回滚策略

【rollbackFor属性】


当发生什么样的异常(包括其子类)时,进行回滚

对于Spring框架,默认情况下,RuntimeException类型才会回滚;对于编译时异常和Error,是不会回滚的


所以,需要修改Spring的默认回滚策略

例如:rollbackFor = Exception.class

Tips:


⑴ RuntimeException:运行时异常


例如FileNotFoundException,这种可以遇见到的异常

⑵ Exception:编译期异常/检查异常/受控异常

与rollbackFor相对的是:


【noRollbackFor】属性


遇到指定的异常类型(包括其子类),不进行回滚

6.4.事务超时属性

【timeout】属性


timeout = 超时时间(秒)

如果一个事务的执行时间,超过了指定的时长(n秒),就被视为超时。当该事务执行完毕时,会抛出异常


org.springframework.transaction.TransactionTimedOutException: Transaction timed out: deadline was ???

6.5.只读属性

【readOnly】属性


一般查询的时候,会将该属性设置为true,表明为只读。这样数据库底层会对查询进行优化处理,提高查询的效率

注意:当一个连接对象(Connection)设置了只读属性,则其再操作增删改时,会报错:


java.sql.SQLException: Connection is read-only. Queries leading to data modification are not allowed

6.6.Transaction属性设置示例

【示例一】

// 事务传播行为为当有事务时就加入,没有就新建一个事务;
// 默认隔离级别;
// 事务回滚策略:Exception及其子异常
// 超时:3秒超时
@Transactional(propagation = Propagation.REQUIRED,
        isolation = Isolation.DEFAULT,
        rollbackFor = Exception.class,
        timeout = 3)
public void testTransaction() { }

【示例二】

 // 查询业务操作,设置连接属性为只读,提高效率
    @Transactional(readOnly = true)
    public void testQuery() { }

6.7.代码示例

【SQL语句】

USE test;

CREATE TABLE books(
  id INT PRIMARY KEY AUTO_INCREMENT,
  name VARCHAR(100) NOT NULL,
  price INT NOT NULL,
  stock INT NOT NULL
);

INSERT INTO books(name, price, stock)
VALUES('小王子', 34, 100),
('圣经', 48, 100),
('福尔摩斯探案集', 84, 100);

【Book类(JavaBean)】

属性:Integer id, String name, Integer price, Integer stock;提供get和set,有参无参构造,重写toString方法

【核心配置文件】





    
    
    
    




    




    




【BookDao接口】

package com.test.tx.dao;

import java.util.List;

import com.test.tx.bean.Book;

public interface BookDao {

    int updateBookPrice(String name, Integer price);

    int updateBookStock(String name);

    Book getBook(String name);

    List queryBooks();

}

【BookDaoImpl实现类】

package com.test.tx.dao;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Repository;

import com.test.tx.bean.Book;

@Repository
public class BookDaoImpl implements BookDao {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Override
    public int updateBookPrice(String name, Integer price) {
        String sql = "UPDATE test.books SET price = ? WHERE name = ?";
        return jdbcTemplate.update(sql, price, name);
    }

    @Override
    public int updateBookStock(String name) {
        String sql = "UPDATE test.books SET stock = stock - 1 WHERE name = ?";
        return jdbcTemplate.update(sql, name);
    }

    @Override
    public Book getBook(String name) {
        String sql = "SELECT id, name, price, stock FROM test.books WHERE name = ?";
        RowMapper rowMapper = new BeanPropertyRowMapper(Book.class);
        return jdbcTemplate.queryForObject(sql, rowMapper, name);
    }

    @Override
    public List queryBooks() {
        String sql = "SELECT id, name, price, stock FROM test.books";
        RowMapper rowMapper = new BeanPropertyRowMapper(Book.class);
        return jdbcTemplate.query(sql, rowMapper);
    }

}

【BookService接口】

package com.test.tx.service;

import java.util.List;

import com.test.tx.bean.Book;

public interface BookService {

    int updateBookPrice(String name, Integer price) throws Exception;

    int updateBookStock(String name) throws Exception;

    Book getBook(String name);

    List queryBooks();

}

【BookServiceImpl实现类】

package com.test.tx.service;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import com.test.tx.bean.Book;
import com.test.tx.dao.BookDao;

@Service
public class BookServiceImpl implements BookService {

    @Autowired
    private BookDao bookDao;

    /*
     * 测试超时
     * 
     * org.springframework.transaction.TransactionTimedOutException: Transaction
     * timed out: deadline was ???
     * 
     * @Transactional(timeout = 3)
     */
    // ---------------------------------------------------------------------------------
    /*
     * 测试更新时,设置连接为只读
     * 
     * Caused by: java.sql.SQLException: Connection is read-only. Queries
     * leading to data modification are not allowed
     * 
     * @Transactional(readOnly = true)
     */
    // ---------------------------------------------------------------------------------

    // 这里设置事务的传播行为是:不管有没有事务,都会开启一个新的事务
    // 是为了测试MulService的testTx方法【因为和下面的更新图书库存操作是两个事务,所以这个事务出错了,但是不会回滚。最终效果是价格变了,但库存没有变】
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public int updateBookPrice(String name, Integer price) throws Exception {
        // 测试超时【让线程休眠】
        // Thread.sleep(4000);

        return bookDao.updateBookPrice(name, price);
    }

    /*
     * 如果不设置回滚策略,则会造成执行MulService的testTx方法时,图书的价格变了,但是库存没有变,事务并没有回滚
     * 
     * @Transactional(rollbackFor = Exception.class)
     */
    // ---------------------------------------------------------------------------------

    // 这里设置事务的传播行为是:不管有没有事务,都会开启一个新的事务
    // 是为了测试MulService的testTx方法【因为和上面的更新图书价格操作是两个事务,所以这个事务出错了,但是不会回滚。最终效果是价格变了,但库存没有变】
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public int updateBookStock(String name) throws Exception {
        // 这里抛出一个编译时异常【Spring默认是不回滚的】
        // FileInputStream fis = new FileInputStream("a/a/a/a");

        // 这里有一个算数异常
        int i = 1 / 0;
        return bookDao.updateBookStock(name);
    }

    // 设置为只读的,提高效率
    @Transactional(readOnly = true)
    public Book getBook(String name) {
        return bookDao.getBook(name);
    }

    // 设置为只读的,提高效率
    @Transactional(readOnly = true)
    public List queryBooks() {
        return bookDao.queryBooks();
    }

}

【MulService类(用于调用BookServiceImpl的两个业务方法)】

package com.test.tx.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class MulService {

    @Autowired
    private BookService bookService;

    @Transactional
    public void testTx(String name, Integer price) throws Exception {
        bookService.updateBookPrice(name, price);

        bookService.updateBookStock(name);
    }

}

【测试类】

package junit.test;

import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.test.tx.bean.Book;
import com.test.tx.service.BookService;
import com.test.tx.service.MulService;

public class TestTx {
    private ApplicationContext ioc = new ClassPathXmlApplicationContext("???.xml");

    @Test
    // 测试执行更新图书价格操作
    public void test1() throws Exception {
        BookService bookService = ioc.getBean(BookService.class);
        bookService.updateBookPrice("小王子", 100);
    }

    @Test
    // 测试更新图书的库存和价格【两个Dao操作】
    public void test2() throws Exception {
        MulService mulService = ioc.getBean(MulService.class);
        String name = "小王子";
        Integer price = 200;
        mulService.testTx(name, price);
    }

    @Test
    public void test3() {
        BookService bookService = ioc.getBean(BookService.class);
        Book book = bookService.getBook("小王子");
        System.out.println(book);
    }

}

7.7、基于XML的声明式事务

7.1.相关标签

 

用于声明通知的事务标签。它有tx:attributes子标签


 tx:advice的子标签,在其里面 声明开启事务的方法标签



 name:需要开启事务的方法的名字
 isolation:事务隔离级别
 no-rollback-for:事务不回滚策略
 propagation:事务传播行为
 read-only:只读属性
 rollback:事务回滚策略
 timeout:超时【默认值为-1,即不超时】

Tips:name属性可以使用* 来代替多个字符


例如:getUser方法名,可以用get* 来代替

设置开启事务方法的Transaction的属性


在其里面声明切入点表达式和组织者


用于声明切入点表达式


用于声明组织者,需要引用事务的通知的id(tx:advice)和切入点表达式(aop:pointcut)


该标签用于匹配需要开启事务的业务逻辑方法

7.2.示例


    
        
        
        
        

        
        
        
        
    



    
    
    
    

转载请注明:CodingBlog » Spring 06 声明式事务

喜欢 (0)or分享 (0)
发表我的评论
取消评论

*

表情