【SSM框架系列】Spring - JdbcTemplate&声明式事务

你离开我真会死。 提交于 2020-02-28 01:23:14

JdbcTemplate概述

  • 以往使用jdbc时,每次都需要自己获取PreparedStatement,执行sql语句,关闭连接等操作。操作麻烦冗余,影响编码的效率。

  • Spring把对数据库的操作在jdbc上面做了深层次的封装,使用spring的注入功能,可以把DataSource注册到JdbcTemplate(jdbc模板)之中,这样我们只需要做一些简单的操作(eg:编写SQL语句、传递参数)就可以了。

  • spring框架根据不同持久层方案为我们提供了不同的JdbcTemplate(jdbc模板类)。例如:操作关系型数据的JdbcTemplate和HibernateTemplate,操作nosql数据库的RedisTemplate,操作消息队列的JmsTemplate等等

QuickStart

导入spring-jdbc和spring-tx坐标

<dependencies>

    <!-- Spring上下文坐标,如果使用到了Spring的上下文容器,需要导入-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.0.5.RELEASE</version>
    </dependency>
    <!-- 使用Spring JdbcTemplate需要导入下面两个坐标 -->
    <!-- 导入spring-jdbc坐标 -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-jdbc</artifactId>
        <version>5.0.5.RELEASE</version>
    </dependency>
    <!-- 导入spring-tx事务管理坐标,如果不使用事务管理,可以不导 -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-tx</artifactId>
        <version>5.0.5.RELEASE</version>
    </dependency>
    <!-- 因为操作Mysql数据库,需要导入数据库驱动坐标 -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.6</version>
    </dependency>
    <!-- 数据源选择使用c3p0连接池 -->
    <dependency>
        <groupId>c3p0</groupId>
        <artifactId>c3p0</artifactId>
        <version>0.9.1.2</version>
    </dependency>
    <!-- 使用单元测试 -->
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
    </dependency>
</dependencies>

在测试类中创建JdbcTemplate对象并使用

public class AccountTest {

    @Test
    public void test1() throws PropertyVetoException {

        //1. 创建JdbcTemplate模板对象
        JdbcTemplate jdbcTemplate = new JdbcTemplate();

        //2. 设置数据源ComboPooledDataSource对象
        //创建DataSource对象,并设置连接四要素
        ComboPooledDataSource dataSource = new ComboPooledDataSource();
        dataSource.setDriverClass("com.mysql.jdbc.Driver");
        dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/test");
        dataSource.setUser("root");
        dataSource.setPassword("root");

        //JdbcTemplate依赖于DataSource
        jdbcTemplate.setDataSource(dataSource);

        //3. 执行增删改查操作
        int row = jdbcTemplate.update("insert into account values (?,?)", "赵丽颖", 666);
        System.out.println(row);
    }
}

Spring容器创建并维护JdbcTemplate及相关对象

1)思路分析

  • Spring的核心之一就是Ioc,控制反转,也就是由Spring容器负责对象的创建和对象间依赖关系的管理。上述代码中DataSource、JdbcTemplate对象以及他们之间的关系都是由我们手动创建,并且手动设置依赖关系的。

  • 经过观察,JdbcTemplate类可以通过无参构造创建该类对象,同时又setDataSource()方法用于设置数据源依赖,所以JdbcTemplate类的对象完全可以交给Spring容器管理,数据源DataSource可以交给Spring容器管理,那我们就可以使用Spring容器管理并维护上述两个对象。

2)代码实现

导入坐标:因为要使用到Spring容器创建对象并维护对象依赖关系,所以要在pom.xml中确定导入了spring-context坐标。

    <!-- Spring上下文坐标,使用到了Spring的上下文容器,需要 -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.0.5.RELEASE</version>
    </dependency>

Xml配置:在applicationContext.xml文件中先配置数据源,然后配置JdbcTemplate,最后将数据源注入到JdbcTemplate中。

<!-- 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:///test"></property>
    <property name="user" value="root"></property>
    <property name="password" value="root"></property>
</bean>

<!-- JdbcTemplate对象 -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
    <property name="dataSource" ref="dataSource"></property>
</bean>

Java代码:通过Spring容器获得JdbcTemplate对象,调用JdbcTemplate对象的update方法,传入sql语句及参数完成插入数据的操作

@Test
public void test2() throws PropertyVetoException {

    //1. 获取JdbcTemplate模板对象(Spring已经将DataSource注入其中)
    ApplicationContext ac= new ClassPathXmlApplicationContext(
        "applicationContext.xml");
    JdbcTemplate jdbcTemplate = ac.getBean(JdbcTemplate.class);

    //2. 不需要设置数据源ComboPooledDataSource对象

    //3. 执行增删改查操作
    int row = jdbcTemplate.update("insert into account values (?,?)", "shheima", 60000);
    System.out.println(row);
}

抽取jdbc配置

  • 目前的jdbc四要素已经脱离了代码,已经实现了解耦,之后如果数据库信息修改后直接修改配置文件即可。

  • 但是目前数据库四要素的配置耦合进了Spring的配置文件,为了更方便的维护,建议将该信息抽取到一个独立properties文件中。

  • 提取数据源的基本连接信息到配置文件jdbc.properties(名字任意,格式为properties)

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql:///test
jdbc.username=root
jdbc.password=root

引入properties文件,并修改DataSource注入基本参数的配置,具体如下:

<!-- 导入jdbc.properties文件。classpath表示文件在类路径下 -->
<context:property-placeholder location="classpath:jdbc.properties"/>

<!-- DataSource对象 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
    <!--
       property的name属性名称通过set方法截取;因为属性值为String,所以通过value属性设置
        通过${key}的方式可以从properties文件中读取key对应的值。
      -->
    <property name="driverClass" value="${jdbc.driver}"></property>
    <property name="jdbcUrl" value="${jdbc.url}"></property>
    <property name="user" value="${jdbc.username}"></property>
    <property name="password" value="${jdbc.password}"></property>
</bean>

<!-- JdbcTemplate对象 -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
    <property name="dataSource" ref="dataSource"></property>
</bean>

如果有多个properties文件需要引入可以使用通配符:

<!--如果需要导入多个文件,可以使用通配符 -->
<context:property-placeholder location="classpath:*.properties"/>

JdbcTemplate常用操作

jdbcTemplate更新数据库常用方法

  • update (更新数据,也就是增删改)
  • queryForObject (单行查询)
  • query (多行查询)
  • queryForObject (单值查询)

JdbcTemplate常用操作-增删改操作

JdbcTemplate对象的update方法可以完成增删改的操作,只是传入的sql语句及对应参数不同。

语法:对数据库进行增删改的操作,返回影响的行数
jdbcTemplate.update(sql语句, sql语句参数列表)	

代码如下:

@RunWith(SpringJUnit4ClassRunner.class)						//指定测试器
@ContextConfiguration("classpath:applicationContext.xml")	//指定Spring配置文件并加载
public class JdbcTemplatetTest {

    @Autowired			//自动注入JdbcTemplate对象
    private JdbcTemplate jdbcTemplate;

    /**
     * testUpdate:测试删除
     */
    @Test
    public void testDelete(){
        int row = jdbcTemplate.update("delete from account where name=?","nanjingheima");
        System.out.println(row);
    }

    /**
     * testUpdate:测试修改
     */
    @Test
    public void testUpdate(){
        int row = jdbcTemplate.update("update account set money=? where name=?", 500000,"hzheima");
        System.out.println(row);
    }
}

JdbcTemplate常用操作-查询操作

JdbcTemplate对象的query**()方法可以完成查询的操作。

多行查询
List<T> query(sql语句, new BeanPropertyRowMapper<T>(T.class))	
				查询并将结果通过BeanPropertyRowMapper将查询的结果集封装到List集合中
List<T> query(sql语句, new BeanPropertyRowMapper<T>(T.class), sql语句参数列表)
				查询并将结果通过BeanPropertyRowMapper将查询的结果集封装到List集合中
单行查询
T queryForObject(sql语句, new BeanPropertyRowMapper<T>(T.class), sql语句参数列表)
    			单行查询
				查询并将结果通过BeanPropertyRowMapper将查询结果封装到javaBean对象中
单值查询
要求的查询结果类型 queryForObject(sql语句, 要求的查询结果类型.class)
				查询并将结果通过BeanPropertyRowMapper将聚合查询结果转化到要去的结果类型
要求的查询结果类型 queryForObject(sql语句, 要求的查询结果类型.class, sql语句参数列表)
				查询并将结果通过BeanPropertyRowMapper将聚合查询结果转化到要去的结果类型

代码如下:

@RunWith(SpringJUnit4ClassRunner.class)						//指定测试器
@ContextConfiguration("classpath:applicationContext.xml")	//指定Spring配置文件并加载接卸
public class JdbcTemplatetTest {

    @Autowired			//自动注入JdbcTemplate对象
    private JdbcTemplate jdbcTemplate;
	/**
     * testQueryCount:聚合查询
     */
    @Test
    public void testQueryCount(){
        long count = jdbcTemplate.queryForObject("select count(*) from " +
                "account",long.class);
        System.out.println(count);
        count = jdbcTemplate.queryForObject("select count(*) from " +
                "account where money = ?",long.class,60000);
        System.out.println(count);
    }

    /**
     * testQueryOne:查询单个
     */
    @Test
    public void testQueryOne(){
        Account account = jdbcTemplate.queryForObject("select * from account " +
                "where name = ?",
                new BeanPropertyRowMapper<Account>(Account.class) ,
                "hzheima");
        System.out.println(account);
    }

    /**
     * testQuerySome:按条件查询部分
     */
    @Test
    public void testQuerySome(){
        List<Account> accounts = jdbcTemplate.query("select * from account where money = ?",
                new BeanPropertyRowMapper<Account>(Account.class), 60000);
        System.out.println(accounts);
    }

    /**
     * testQueryAll:查询所有
     */
    @Test
    public void testQueryAll(){
        List<Account> accounts = jdbcTemplate.query("select * from account",
                new BeanPropertyRowMapper<Account>(Account.class));
        System.out.println(accounts);
    }
}

补充:

批量更新(增删改)

//批量增删改,batchArgs为批量更新的参数列表,类型为Object[]数组类型的List集合
jdbcTemplate.batchUpdate(String sql, List batchArgs);
/**
 *testBatechUpdate:批量更新,批量 insert,update,delete
 */
@Test
public void testBatechUpdate() {
    String sql = "insert into account(name, money) values(?,?)";
    List<Object[]> batchArgs = new ArrayList<Object[]>();
    batchArgs.add(new Object[] {"zhangsan",2000});
    batchArgs.add(new Object[] {"lisi",2000});
    batchArgs.add(new Object[] {"wangwu",2000});
    jdbcTemplate.batchUpdate(sql, batchArgs);
}

自定义绑定(使用场景较少)

除了使用系统提供的RowMapper的实现类之外,我们也可以自己手动编写RowMapper实现类,以实现特殊的结果集到javaBean的封装转化,实例代码如下:

Account account = jdbcTemplate.queryForObject(
    "select * from account where money = ?",
    new Object[]{60000L},
    new RowMapper<Account>() {
        //重写该方法时只需要将获取的结果集中单个结果封装到某个JavaBean中,
        //query方法会根据实际情况选择选择返回一个javaBan对象还是一个List
        public Account mapRow(ResultSet rs, int rowNum) throws SQLException {
            Account account = new Account();
            account.setUsername(rs.getString("name"));
            account.setMoney(rs.getString("money"));
            return account;
        }
    });

执行DDL语句(使用场景很少)

Spring的JdbcTemplate也可以使用execute(…)方法运行任意SQL语句。所以,该方法通常用于DDL语句。下面代码创建一个表:

jdbcTemplate.execute("create table mytable (id integer, name varchar(100))");

Spring事务管理

Spring事务管理主要应用在软件分层之后的业务层,也就是Service层。一个service层的操作依赖的多个dao层操作要被事务管理起来,保证数据安全准确。eg:转账。

Spring事务管理核心API

Spring事务管理的三个高层接口

  • PlatformTransactionManager:Spring提供的事务管理器核心接口,内部规定了常用的事务管理的规则。

  • TransactionDefinition:Spring提供的封装事务属性的接口,用于封装和操作事务的属性

  • TransactionStatus:Spring提供的封装事务状态的接口,用于封装事务的状态

PlatformTransactionManager

Spring提供的事务管理器接口,整个Spring进行事务管理的核心接口,内部规定了常用的操作事务的规则。

a) 接口中规定的操作事务的方法:

操作事务的方法
TransactionStatus getTransaction
(TransactionDefination defination)
根据事务定义信息获取事务状态信息
void commit(TransactionStatus status) 提交事务
void rollback(TransactionStatus status) 回滚事务

根据上述方法,

​ TransactionStatus getTransaction(TransactionDefination defination)

我们可以推断:PlatformTransactionManager根据TransactionDefinition对象获得 TransactionStatus 对象

  • 三者的关系平台事务管理器(PlatformTransactionManager)根据 事务属性管理器(TransactionDefinition)中定义的事务属性对事务进行管理;事务运行过程中,每一个时间点都有对应的事务状态信息(TransactionStatus)。

  • 也就是说,干活的是Manager(PlatformTransactionManager),根据事务属性管理器(TransactionDefinition)的配置信息干活,在干活的过程中会有一些状态信息,通过TransactionStatus体现。

b) 不同的Dao层技术则有不同的实现类

PlatformTransactionManager是一个接口,只负责制定规范,那具体的实现是哪些类呢?Spring根据不同的数据持久层技术提供了不同实现类:

  • Dao 层技术是jdbc、 mybatis、Apache DBUtils时:org.springframework.jdbc.datasource.DataSourceTransactionManager

  • Dao层技术是hibernate时:org.springframework.orm.hibernate5.HibernateTransactionManager

TransactionDefinition

Spring提供的封装事务属性的接口,用于规定封装事务的属性,通过该接口实现类对象可以获取(子类对象可以设置)事务属性。

a) 接口中规定的获取事务属性的方法:

方法名 说明
int getIsolationLevel() 获取事务的隔离级别,隔离级别用于解决事务并发访问读问题
int getPropagationBehavior() 获取事务的传播行为,解决的就是两个事务方法事务统一性的问题
int getTimeout() 获取超时时间
boolean isReadOnly() 判断当前事务是否只读
  • 接口中只规定了获取的方法,在其实现类中有设置的方法,eg:DefaultTransactionDefinition

  • 事务属性需要在进行声明式事务配置时,需要在配置文件中对于事务属性进行配置,以便Spring框架进行解析。

b) 事务的四个特性:

ACID:原子性、一致性、隔离性、持久性。

  • Atomic(原子性):事务中包含的操作被看做一个逻辑单元,这个逻辑单元中的操作要么全部成 功,要么全部失败。

  • Consistency(一致性):事务完成时,数据必须处于一致状态,数据的完整性约束没有被破坏,事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。

  • Isolation(隔离性):事务允许多个用户对同一个数据进行并发访问,而不破坏数据的正确性 和完整性。同时,并行事务的修改必须与其他并行事务的修改相互独立。

  • Durability(持久性):事务结束后,事务处理的结果必须能够得到持久化。

c) 事务属性:隔离级别(理解)

在并发访问的时候,会出现一些读问题。

读问题

  • 脏读:一个事务读取了另外一个事务改写但还未提交的更新数据,如果这些数据被回滚,则读到的数据是无效的。
    • eg:小明工资表,老板算工资的时候被小明看到了。
  • 不可重复读:在同一个事务中,多次读取同一个数据返回的结果有所不同。换句话说就是,后续读取可以读取到另一个事务已提交的更新数据。相反,“可重复读”在同一个事务中多次读取数据时,能保证数据时一样,也就是,后续读取不能读取到另一个事务已经提交的更新数据。
    • eg:小明买手机前后老婆查账。
  • 幻读:一个事务读取了(按照某个规则的)全部数据后,另一个事务插入了一些记录,当后续再次查询的时候,幻读就发生了。在后续查询中,第一个事务就会发现有一些原来没有的记录。
    • eg:老婆查岗打印流水,又发现消费多了。

设置隔离级别,可以解决事务并发产生的读问题,如脏读、不可重复读和虚读(幻读)。

  • ISOLATION_DEFAULT 默认级别,使用数据库内部默认级别(mysql - Repeatable read,其他大多数是 - Read committed)

  • ISOLATION_READ_UNCOMMITTED 读未提交:一个事务可以读取另一个未提交事务的数据,该级别下容易上述所读有问题。性能最高,安全性最差。

  • ISOLATION_READ_COMMITTED 读提交:一个事务要等另一个事务提交后才能读取数据。能解决脏读问题,但是可能会有不可重复读、幻读的问题。

  • ISOLATION_REPEATABLE_READ 重复读,在开始读取数据(事务开启)时,不再允许修改操作。能解决脏读、不可重复读的问题,但是可能会有幻读的问题。

  • ISOLATION_SERIALIZABLE 序列化,解决所有三个问题,相当于锁表安全性高,串行化处理事务,但是非常耗数据库性能,效率低下,一般不用。

列表如下(√表示可能出现,×表示不会出现):

隔离级别\读问题 脏读 不可重复读 幻读
Read uncommitted读未提交
Read commited读提交 ×
Repeatable read重复读 × ×
Serializable × × ×

上述事务的四大特性、四个隔离级别是原来是在数据库内部规定的,Spring从数据库引入了 过来。但是接下来的七种传播行为和数据库没有直接关系,是Spring为了解决实际开发中的问题而引入的。

d) 事务属性:传播行为

即然是传播,那么至少有两个东西,才可以发生传播。单体不存在传播这个行为。

事务传播行为(propagation behavior)指的就是当一个事务方法A调用另一个事务方法B时,事务方法B应该以何种事务特性进行。是继续在调用者methodA的事务中运行呢,还是为自己开启一个新事务运行,这就是由methodB的事务传播行为决定的。

事务传播行为是为了解决业务方法相互调用时,事务如何管理的问题。

通常情况下,一个业务方法就是一个事务。但是多个业务方法相互调用的时候可能会出现问题。

eg:取钱打印小票

取钱 required

{

​ 插卡 required

​ 输密码 required

​ 吐钱 required

​ 打印小票 requires_new

}

七种事务传播行为(当前方法:被调用的方法):

  • PROPAGATION_REQUIRED:表示当前方法必须运行在事务中。如果调用者事务存在,当前方法就会在该事务中运行;否则,会启动一个新的事务。一般的选择(默认值)。

  • PROPAGATION_SUPPORTS:表示当前方法不需要事务上下文,但是如果当前事务存在的话,那么该方法会在这个事务中运行。

  • PROPAGATION_MANDATORY:表示当前方法必须在事务中运行,如果当前事务不存在,则会抛出一个异常。

  • PROPAGATION_REQUERS_NEW:表示当前方法必须运行在它自己的事务中,一个新的事务将被启动。如果存在当前事务,该方法执行期间,当前事务会被挂起。

  • PROPAGATION_NOT_SUPPORTED:表示当前方法不应该运行在事务中。如果存在当前事务,该方法运行期间,当前事务会被挂起。

  • PROPAGATION_NEVER:表示当前方法不应该运行在事务上下文中。如果存在当前事务,则会抛出异常。

  • PROPAGATION_NESTED:表示如果当前已经存在一个事务,那么该方法将会在嵌套事务中运行。嵌套的事务可以独立于当前事务进行独立的提交和回滚。如果当前事务不存在,那么其行为与PROPAGATION_REQUIRED一样。注意各厂商对这种传播行为的支持不同,可以参考资源管理器的文档来确认他们是否支持嵌套事务。

e) 事务其他属性

超时时间:默认值是-1,没有超时限制。如果有,以秒为单位进行设置。一般使用默认。
是否只读:建议查询操作时设置为只读,效率更高。

事务中的readOnly属性表示对应的事务会优化为只读事务。如果值为true就会告诉Spring我这个方法里面没有insert或者update,你只需要提供只读的数据库Connection就行了,这种执行效率会比read-write的Connection高。

如果设置了只读事务,Mysql数据库增删改会报错,Oracle无影响(不支持)

TransactionStatus

Spring提供的封装事务状态的接口,用于封装事务的状态。

事务的状态是事务自身的属性,在事务管理不同的时间段,事务会表现出不同的状态,所以不需要配置。

a) 接口中规定的判断事务状态的方法:

方法名 说明
boolean isNewTransaction() 是否是新事务。如果返回false,说明当前事务已存在,或者该操作没在事务环境中。
boolean hasSavepoint() 是否存在回滚点
boolean isRollbackOnly() 判断当前事务是否被设置了rollback-only
boolean isCompleted() 事务是否已结束

声明式事务概述

  • Spring 的声明式事务就是采用声明的方式来设置事务的属性参数,事务会按照指定的规则自动管理运行。

  • 这里所说的声明,指在配置文件中配置(或添加注解)<事务的属性参数>,用在Spring 配置文件(注解)中声明的事务来代替用代码编写的事务。

  • 业务代码运行过程中,Spring容器(中的事务管理器对象)跟据配置文件中的配置参数,动态的把事务加入到业务代码中。(AOP思想)

  • (编码时)事务管理不侵入业务组件。编写业务代码的时候并没有体现事务管理的代码。

  • 事务管理策略维护方便。如果需要修改事务管理策略,只要在配置文件上修改,无需改变代码重新编译。

基于XML的声明式事务管理

配置切点

<!--pointcut 目标对象  内部的方法就是切点-->
<bean id="accountService" class="cs.wy.service.impl.AccountServiceImpl">
	<property name="accountDao" ref="accountDao"/>
</bean>

配置通知(事务管理器)

<!-- advice 配置通知-事务管理器就是通知(增强)所在的类 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
	<tx:attributes>
		<tx:method name="*"/>
	</tx:attributes>
</tx:advice>

<!--
	jdbc 使用的(平台)事务管理器是 DataSourceTransactionManager

	事务管理管理的是Service层,service层需要调用dao层操作数据库,
	dao层依赖DataSource管理连接池并连接数据库
	所以事务管理器中需要注入DataSource
-->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
	<property name="dataSource" ref="dataSource"></property>
</bean>

配置织入

<!-- weaving 配置事务的织入。 增强方法 + 目标方法 -->
<aop:config>
    <aop:advisor advice-ref="txAdvice"
                 pointcut="execution(* cs.wy.service.impl.*.*(..))"/>
</aop:config>

基于XML的声明式事务管理参数详解

重点分析如下配置:

<!-- advice  配置通知-事务管理器就是通知(增强)所在的类 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
    <tx:attributes>
        <tx:method name="*"/>
    </tx:attributes>
</tx:advice>

其中:

tx:advice		
		advice,增强方法所在的类(transactionManager的class)
				
tx:attributes
		具体的事务需要配到这个标签里面

tx:method		
		每一个tx:method代表一个类别事务
		在这个标签里面的属性上会配置某些业务逻辑的方法(目标方法),并同时配置对应的事务管理逻辑。
		
tx:nethod ~	name
		name属性,用于配置被切点表达式拦截的所有方法中,哪些业务逻辑方法被当前事务管理。
		该属性并非Spring事务本身的属性,而是为了让该事务匹配关联到某些业务逻辑方法。
		name值可以使用*统配,比方说*代表所有方法,save*代表所有save开头的方法
		
tx:nethod ~	isolation				
		isolation属性,配置当前事务的隔离级别;如不设置或设置DEFAULT,使用默认的隔离级别。
		mysql默认的Repeatable read,其他大多数是Read committed。
		
tx:method ~ propagation
		propagation属性,配置当前事务的传播行为。不设置则使用默认值REQUIRED。
		
tx:method ~ timeout
		timeout属性,配置当前事务的超时时间,不设置则使用默认值-1,表示不限制。一般不设置。
tx:method ~ readonly
		readonly属性,值为boolean类型。默认值为false。

关于tx:attributes

a. 如果不配置该标签,所有切点方法都会单独开启属性为默认值的事务
b. 如果配置该标签,内部tx:method中name匹配的方法会按照当前配置开启事务,
				其他未匹配成功的方法会单独开启属性为默认值的事务
c. 存在多个tx:method标签时,会按照配置顺序挨个匹配,直到匹配成功,未匹配成功开启默认事务     

<tx:attributes>  
    <tx:method name="add*" propagation="REQUIRED"/>  
    <tx:method name="upd*" propagation="REQUIRED"/>  
    <tx:method name="del*" propagation="REQUIRED"/> 
    <tx:method name="*" propagation="SUPPORTS" read-only="true" />  
</tx:attributes>  

关于切点表达式:多个切点表达式可以进行逻辑运算连接(主要使用或||):

<aop:pointcut id="tranpc" expression="execution(* cs.wy.user.service.*.*(..))|| execution(* cs.wy.goods.service.*.*(..))"/>  

关于readonly

对于mysql数据库:
    如果是查询操作,建议设为true,优化查询性能;其他设为false或者不设置使用默认值。
    增删改操作如果设置为true,则会抛出异常。
对于Oracle数据库:
	无效果

基于注解的声明式事务控制

使用基于注解的声明式事务控制,步骤如下:

1、 在xml中开启自动代理

<aop:aspectj-autoproxy/>

2、然后在方法或类上配置@Transactional注解

1. 配置在类上表示该类下所有方法均开启事务
2. 配置在方法上,表示该方法开启事务
  1. 推荐做法:
在类上添加@Transactional统一开启事务,
如有需要,可以在特定方法上添加@Transactional用于配置特属性。
@Transactional(readOnly = true,,propagation = Propagation.SUPPORTS)
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!