spring第四天---Spring中事务控制

Spring第四天—Spring中事务控制

1、 Spring中JdbcTemplate
2、 Spring基于AOP的事务控制
3、 Spring中的事务控制
基于XML的
基于注解的

1.1 Spring中JdbcTemplate

JdbcTemplate是Spring框架中提供的一个对象,对原始的JDBC API进行简单封装,其用法与DBUtils类似.

对应jar包依赖:

spring-jdbc-5.0.2.RELEASE.jar,除了要导入这个 jar 包
外,还需要导入一个 spring-tx-5.0.2.RELEASE.jar(它是和事务相关的)。

  • 操作关系型数据的:
    JdbcTemplate
    HibernateTemplate
  • 操作 nosql 数据库的:
    RedisTemplate
  • 操作消息队列的:
    JmsTemplate

JdbcTemplate的增删改查操作

使用JdbcTemplate实现增删改

JdbcTemplate的增删改操作可以使用其update(“SQL语句”, 参数…)方法


 //1.获取容器
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//2.获取对象
JdbcTemplate jt = ac.getBean("jdbcTemplate",JdbcTemplate.class);
//3.执行操作
//保存
jt.update("insert into account(name,money)values(?,?)","eee",3333f);
//更新
jt.update("update account set name=?,money=? where id=?","test",4567,7);
//删除
jt.update("delete from account where id=?", 8);

使用JdbcTemplate实现查询

//查询所有
List<Account> accounts = jt.query("select * from account where money > ?",new BeanPropertyRowMapper<Account>(Account.class),1000f);

for (Account account:accounts) {
    System.out.println(account);
}
//查询一个
List<Account> accounts = jt.query("select * from account where money > ?",new BeanPropertyRowMapper<Account>(Account.class),1000f);
System.out.println(accounts.isEmpty()?"没有内容":accounts.get(0));

//查询返回一行一列(使用聚合函数,但不加group by子句)
Long count = jt.queryForObject("select count(*) from account where money > ?",Long.class,1000f);
System.out.println(count);

在DAO层使用JdbcTemplate

按原有方式

多个daoImpl 就都要 声明JdbcTemplate 也都要 set,会显得比较麻烦。


/**
 * @author Ven
 * @version 1.0 2020/12/14
 * 账户的持久层实现类   //这种方式可以基于xml ioc,也可以基于注解ioc,两种都可以,但第一种extends JdbcDaoSupport 继承
 * spring的JdbcDaoSupport不好用注解,因为jdbcTemplate写在class里。
 * 但AccountDaoImpl2这种写法 多个daoImpl 就都要 声明JdbcTemplate 也都要 set,会显得比较麻烦。
 */
public class AccountDaoImpl2 implements IAccountDao {

    private JdbcTemplate jdbcTemplate;

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

    public Account findAccountById(Integer accountId) {

        List<Account> accounts = jdbcTemplate.query("select * from account where id=?", new BeanPropertyRowMapper<Account>(Account.class), accountId);
        return accounts.isEmpty()?null:accounts.get(0);
    }
    ...

DAO层对象继承JdbcDaoSupport

避免一个DAO 声明一次JdbcTemplate,set一次。但这种方式因为不能修改JdbcDaoSupport类的源代码,DAO层的配置就只能基于xml配置,而不再可以基于注解配置了。

  • 让DAO对象继承Spring内置的JdbcDaoSupport类.在JdbcDaoSupport类中定义了JdbcTemplate和DataSource成员属性,在实际编程中,只需要向其注入DataSource成员即可,DataSource的set方法中会注入JdbcTemplate对象.
  • DAO的实现类中调用父类的getJdbcTemplate()方法获得JdbcTemplate()对象.

JdbcDaoSupport类的源代码如下:

public abstract class JdbcDaoSupport extends DaoSupport {
    
    @Nullable
    private JdbcTemplate jdbcTemplate;	// 定义JdbcTemplate成员变量

    public JdbcDaoSupport() {
    }

    // DataSource的set方法,注入DataSource时调用createJdbcTemplate方法注入JdbcTemplate
    public final void setDataSource(DataSource dataSource) {
        if (this.jdbcTemplate == null || dataSource != this.jdbcTemplate.getDataSource()) {
            this.jdbcTemplate = this.createJdbcTemplate(dataSource);
            this.initTemplateConfig();
        }
    }

    // 创建JdbcTemplate,用来被setDataSource()调用注入JdbcTemplate
    protected JdbcTemplate createJdbcTemplate(DataSource dataSource) {
        return new JdbcTemplate(dataSource);
    }

    // JdbcTemplate的get方法,子类通过该方法获得JdbcTemplate对象
    @Nullable
    public final JdbcTemplate getJdbcTemplate() {
        return this.jdbcTemplate;
    }

    @Nullable
    public final DataSource getDataSource() {
        return this.jdbcTemplate != null ? this.jdbcTemplate.getDataSource() : null;
    }

    public final void setJdbcTemplate(@Nullable JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
        this.initTemplateConfig();
    }

    // ...
}

/**
 * @author Ven
 * @version 1.0 2020/12/14
 * 账户的持久层实现类
 */
public class AccountDaoImpl extends JdbcDaoSupport implements IAccountDao {

    public Account findAccountById(Integer accountId) {

        List<Account> accounts = super.getJdbcTemplate().query("select * from account where id=?", new BeanPropertyRowMapper<Account>(Account.class), accountId);
        return accounts.isEmpty()?null:accounts.get(0);
    }
    ...


对应的bean.xml DAO只需要注入dataSource即可。


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

    <!--配置账户的持久层-->
    <bean id="accountDao" class="com.itheima.dao.impl.AccountDaoImpl">
<!--        <property name="jdbcTemplate" ref="jdbcTemplate"></property>-->
        <property name="dataSource" ref="dataSource"></property>
    </bean>


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

    <!--配置数据源-->
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
        <property name="url" value="jdbc:mysql://localhost:3306/eesy"></property>
        <property name="username" value="root"></property>
        <property name="password" value="root"></property>
    </bean>

</beans>

ps:很多参考了 Spring学习03:数据库操作模板JdbcTemplate

1.2 Spring事务

需导入的jar包:spring-tx-5.0.2.RELEASE.jar
PlatformTransactionManager 此接口是 spring 的事务管理器,它里面提供了我们常用的操作事务的方法。我们一般使用它的实现类,如下:
真正管理事务的对象

  • org.springframework.jdbc.datasource.DataSourceTransactionManager 使用 Spring JDBC 或 iBatis 进行持久化数据时使用
  • org.springframework.orm.hibernate5.HibernateTransactionManager 使用Hibernate 版本进行持久化数据时使用

1.2.1 数据库事务的基础知识【参考自Spring学习04:事务控制[TransactionManager]

事务的四大特性:ACID
  • 1.原子性(Atomicity): 事务包含的所有操作要么全部成功,要么全部失败回滚;成功必须要完全应用到数据库,失败则不能对数据库产生影响.
  • 2.一致性(Consistency):事务执行前和执行后必须处于一致性状态.例如:转账事务执行前后,两账户余额的总和不变.
  • 3.隔离性(Isolation): 多个并发的事务之间要相互隔离.
  • 4.持久性(Durability): 事务一旦提交,对数据库的改变是永久性的.
事务的隔离级别
  • 1.ISOLATION_READ_UNCOMMITTED: 读未提交.事务中的修改,即使没有提交,其他事务也可以看得到.会导致脏读,不可重复读,幻读.
  • 2.ISOLATION_READ_COMMITTED: 读已提交(Oracle数据库默认隔离级别).一个事务不会读到其它并行事务已修改但未提交的数据.避免了脏读,但会导致不可重复读,幻读.
  • 3.ISOLATION_REPEATABLE_READ: 可重复读(Mysql数据库默认的隔离级别).一个事务不会读到其它并行事务已修改且已提交的数据,(只有当该事务提交之后才会看到其他事务提交的修改).避免了脏读,不可重复读,但会导致幻读.
  • 4.ISOLATION_SERIALIZABLE: 串行化.事务串行执行,一个时刻只能有一个事务被执行.避免了脏读,不可重复读,幻读.
    可以通过下面的例子理解事务的隔离级别:
事务A事务B
启动事务查询得到原始值origin=1
启动事务
查询得到值1
将1改成2
查询得到值value1
提交事务B
查询得到值value2
提交事务A
查询得到值value3

对不同的事务隔离级别,事务A三次查询结果分别如下:

事务隔离级别原始值originvalue1value2value3
ISOLATION_READ_UNCOMMITTED12(脏读)22
ISOLATION_READ_COMMITTED112(不可重复读)2
ISOLATION_REPEATABLE_READ1112
ISOLATION_SERIALIZABLE1111

事务的安全隐患有如下三种,他们可以通过设置合理的隔离级别来避免:

  • 脏读: 一个事务读到另外一个事务还未提交(可能被回滚)的脏数据.
  • 不可重复读: 一个事务执行期间另一事务提交修改,导致第一个事务前后两次查询结果不一致.
  • 幻读: 一个事务执行期间另一事务提交添加数据,导致第一个事务前后两次查询结果到的数据条数不同.
脏读不可重复读幻读
ISOLATION_READ_UNCOMMITTED
ISOLATION_READ_COMMITTED
ISOLATION_REPEATABLE_READ
ISOLATION_SERIALIZABLE

1.2.2 spring事务的隔离级别(还可参考事务的四种隔离级别,数据库事务4种隔离级别及7种传播行为(三)

  • 1.ISOLATION_DEFAULT: Spring事务管理的的默认级别,使用数据库默认的事务隔离级别.
  • 2.ISOLATION_READ_UNCOMMITTED: 可以读取未提交数据
  • 3.ISOLATION_READ_COMMITTED: 只能读取已提交数据,解决脏读问题(Oracle默认级别)。即事务B提交了修改记录后,事务A在B提交前和提交后查询得到的结果是不一样的,不可重复读。
  • 4.ISOLATION_REPEATABLE_READ: 可重复读.即两次读取内容一样不会变。(Mysql默认级别) 事务B提交了修改记录后,事务A在B提交前和提交后查询得到的结果是一样的,可重复读。只有A也提交后,值才会改变。
  • 5.ISOLATION_SERIALIZABLE: 串行化. 解决幻影读的问题。事务A未提交前,B不能更改。
事务的传播行为
  • REQUIRED:如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。一般的选择(默认值)
  • SUPPORTS:支持当前事务,如果当前没有事务,就以非事务方式执行(没有事务)
  • MANDATORY:使用当前的事务,如果当前没有事务,就抛出异常
  • REQUERS_NEW:新建事务,如果当前在事务中,把当前事务挂起。
  • NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起
  • NEVER:以非事务方式运行,如果当前存在事务,抛出异常
  • NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行 REQUIRED 类似的操作。
超时时间

Spring默认设置事务的超时时间为-1,表示永不超时。如果有,以秒为单位进行设置。

是否已读

Spring默认设置为false,建议查询操作中设置为true.

TransactionStatus: 事务状态信息对象,提供操作事务状态的方法如下:

  • void flush(): 刷新事务
  • boolean hasSavepoint(): 查询是否存在存储点
  • boolean isCompleted(): 查询事务是否完成
  • boolean isNewTransaction(): 查询是否是新事务
  • boolean isRollbackOnly(): 查询事务是否回滚
  • void setRollbackOnly(): 设置事务回滚

1.3 spring事务配置–基于xml(声明式事务)

1、配置事务管理器


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

2、配置事务的通知

  • 此时我们需要导入事务的约束 tx名称空间和约束,同时也需要aop的

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd">
</beans>
  • 使用tx:advice标签配置事务通知
    • 属性:
      • id:给事务通知起一个唯一标识
      • transaction-manager:给事务通知提供一个事务管理器引用

<!--配置事务的通知-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
    <!--配置事务的属性
        isolation:用于指定事务的隔离级别。默认值是DEFAULT,表示使用数据库的默认隔离级别。
        propagation:用于指定事务的传播行为。默认值是REQUIRED,表示一定会有事务,增删改的选择。查询方法可以选择SUPPORTS.
        read-only:用于指定事务是否只读。只有查询方法才能设置为true。默认值是false,表示读写。
        timeout:用于指定事务的超时时间,默认值是-1,表示永不超时。如果指定了数值,以秒为单位。
        rollback-for:用于指定一个异常,当产生该异常时,事务回滚;产生其他异常时,事务不回滚。没有默认值,表示任何异常都回滚。
        no-rollback-for:用于指定一个异常,当产生该异常时,事务不回滚;产生其他异常时事务回滚。没有默认值,表示任何异常都回滚。
    -->
    <tx:attributes>
        <!--当既有*又有find*,两个都能匹配时,find*优先级更高。 这种写法要求查询方法名 必须都是find开头-->
        <tx:method name="*" propagation="REQUIRED" read-only="false"/>
        <tx:method name="find*" propagation="SUPPORTS" read-only="true"></tx:method>
    </tx:attributes>
</tx:advice>

3、配置AOP中的通用切入点表达式

4、建立事务通知和切入点表达式的对应关系


<!--配置aop-->
    <aop:config>
        <!--配置切入点表达式-->
        <aop:pointcut id="pt1" expression="execution(* com.itheima.service.impl.*.*(..))"/>
        <!--建立切入点表达式和事务通知的对应关系-->
        <aop:advisor advice-ref="txAdvice" pointcut-ref="pt1"></aop:advisor>
    </aop:config>

5、配置事务的属性

是在事务的通知tx:advice标签的内部

其中事务的属性(对应标签为<tx:method>)

  • 1.name:拦截到的方法,可以使用通配符*
  • 2.isolation:用于指定事务的隔离级别。默认值是DEFAULT,表示使用数据库的默认隔离级别。
  • 3.propagation:用于指定事务的传播行为。默认值是REQUIRED,表示一定会有事务,增删改的选择。查询方法可以选择SUPPORTS.
  • 4.read-only:用于指定事务是否只读。只有查询方法才能设置为true。默认值是false,表示读写。
  • 5.timeout:用于指定事务的超时时间,默认值是-1,表示永不超时。如果指定了数值,以秒为单位。
  • 6.rollback-for:用于指定一个异常,当产生该异常时,事务回滚;产生其他异常时,事务不回滚。没有默认值,表示任何异常都回滚
  • 7.no-rollback-for:用于指定一个异常,当产生该异常时,事务不回滚;产生其他异常时事务回滚。没有默认值,表示任何异常都回滚

1.4 spring事务配置–半注解(声明式事务)

导入相关约束,不再赘述。

1、配置事务管理器

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

2、开启spring对注解事务的支持


<!--开启spring对注解事务的支持-->
<tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>

3、在需要事务支持的地方使用@Transacitonal注解

@Transactional注解,其参数与<tx:method>的属性一致.
该注解可以加在接口,类或方法上

  • 对接口加上@Transactional注解,表示对该接口的所有实现类进行事务控制
  • 对类加上@Transactional注解,表示对类中的所有方法进行事务控制
  • 对具体某一方法加以@Transactional注解,表示对具体方法进行事务控制
    三个位置上的注解优先级依次升高。

/**
 * 账户的业务层实现类
 *
 * 事务
 *
 */
@Service("accountService")
@Transactional(propagation = Propagation.SUPPORTS,readOnly = true)  //只读型事务的配置
public class AccountServiceImpl implements IAccountService {

    @Autowired
    private IAccountDao accountDao;

    public Account findAccountById(Integer accountId) {
        return accountDao.findAccountById(accountId);
    }


    @Transactional(propagation = Propagation.REQUIRED,readOnly = false)
    public void transfer(String sourceName, String targetName, Float money) {
        ...
    }

1.4 spring事务配置–纯注解(声明式事务)

其实就是把xml相关的改为配置类来实现创建,且创建完通过注解放到spring容器中。同时告诉spring这是配置类。

1.创建主配置类SpringConfiguration.java

@EnableTransactionManagement事务的注解,表明开启事务控制。


/**
 * @author Ven
 * @version 1.0 2020/12/16
 * spring的配置类,相当于bean.xml
 */
@Configuration
@ComponentScan("com.itheima")
@Import({JdbcConfig.class,TransactionConfig.class})
@PropertySource("jdbcConfig.properties")
@EnableTransactionManagement
public class SpringConfiguration {
}


2.创建和数据库连接相关的配置类

  • @Bean(name = “jdbcTemplate”)告诉spring创建完放到容器中
  • @Value("${jdbc.driver}")和主配置类@PropertySource("jdbcConfig.properties")关联使用

/**
 * @author Ven
 * @version 1.0 2020/12/16
 * 和连接数据库相关的配置类
 */
public class JdbcConfig {

    @Value("${jdbc.driver}")
    private String driver;
    @Value("${jdbc.url}")
    private String url;
    @Value("${jdbc.username}")
    private String username;
    @Value("${jdbc.password}")
    private String password;

    /**
     * 创建JdbcTemplate对象
     * @param dataSource
     * @return
     */
    //  @Bean这样创建完才会进容器
    @Bean(name = "jdbcTemplate")
    public JdbcTemplate createJdbcTemplate(DataSource dataSource){
        return new JdbcTemplate(dataSource);
    }

    /**
     * 创建数据源对象
     * @return
     */
    // 同理,创建完把它放容器中
    @Bean(name = "dataSource")
    public DataSource createDataSource(){
        DriverManagerDataSource ds = new DriverManagerDataSource();
        ds.setDriverClassName(driver);
        ds.setUrl(url);
        ds.setUsername(username);
        ds.setPassword(password);
        return ds;
    }
}

3.创建和事务相关配置类


/**
 * @author Ven
 * @version 1.0 2020/12/16
 * 和事务相关的配置类
 */
public class TransactionConfig {

    /**
     * 用于创建事务管理器对象
     * @param dataSource
     * @return
     */
    @Bean(name = "transactionManager")
    public PlatformTransactionManager createTransacitonManager(DataSource dataSource){
        return new DataSourceTransactionManager(dataSource);
    }
}

4.测试类导入方式变为加载主配置类


/**
 * 使用Junit单元测试:测试我们的配置
 */
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfiguration.class)
public class AccountServiceTest {

    @Autowired
    private IAccountService as;

    @Test
    public void testTransfer(){
        as.transfer("aaa","bbb",100f);

    }

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值