目录
4.使用JdbcTemplate实现增删改查(只有在声明式事务里面用,一般用的还是MyBatis)
1.事务简介
1.事务概念
数据库事务就是一种SQL语句执行的缓存机制,不会单条执行完毕就更新数据库数据,最终根据缓存内的多条语句执行结果统一判定。
* 一个数据库内所有语句成功,可以触发commit提交事务来结束事务,更新数据
* 任意一条语句失败都可以触发rollback回滚结束事务
* 优势:允许在失败情况下,数据回到业务之前的状态
2.事务的特性:ACID
* Atomicity-原子性:事务是一个不可分割的工作单位,事务中的操作要么都执行,要么都不执行
* Consistency-一致性:事务必须使数据库从一个一致性状态变换到另外一个一致性状态
* Isolation-隔离性:事务的隔离性指事务并发执行时,不会被其他事务干扰
* Durability-持久性:一个事务一旦被提交,它对数据库数据的改变就是永久性的
2.JdbcTemplate
* 事务是mysql中的内容,因此只能在执行SQL语句时测试事务功能。Spring对Jdbc进行封装,封装后形成一个框架叫做SpringJdbc,当中的一个类叫做JdbcTemplate,这个类中有各种对数据库中增删改查的方法。
* JdbcTemplate来操作数据库中的各种数据,实现声明式事务的功能
3.配置JdbcTemplate的bean
1. 将JdbcTemplate作为bean来管理,管理时需要数据源属性
2. 创建数据源的bean对象,需要连接数据库的参数:jdbc.properties
3. 创建jdbc.properties
<!--
spring管理数据源
1.在pom.xml文件中加入mysql驱动依赖和数据源依赖
2.创建外部属性文件jdbc.properties
3.在配置文件中引入外部属性文件
4.配置bean信息
-->
<!-- 3.引入外部属性文件-->
<context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder>
<!-- 4.配置数据源-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${jdbc.driver}"></property>
<property name="url" value="${jdbc.url}"></property>
<property name="username" value="${jdbc.user}"></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>
4.使用JdbcTemplate实现增删改查(只有在声明式事务里面用,一般用的还是MyBatis)
* Spring中已经整合了junit,因此可以通过@RunWith注解,指定当前测试类在Spring的测试环境中执行,此时就可以通过注入的方式,直接获取IOC容器中bean
//指定当前测试类在Spring的测试环境中执行,此时可以通过注入的方式直接获取ioc容器中bean
@RunWith(SpringJUnit4ClassRunner.class)
//设置Spring测试环境的配置文件
@ContextConfiguration("classpath:applicationcontext.xml")
public class testJdbcTemplate {
}
* 增删改操作都使用update方法实现
//指定当前测试类在Spring的测试环境中执行,此时可以通过注入的方式直接获取ioc容器中bean
@RunWith(SpringJUnit4ClassRunner.class)
//设置Spring测试环境的配置文件
@ContextConfiguration("classpath:applicationcontext.xml")
public class testJdbcTemplate {
@Autowired
private JdbcTemplate jdbcTemplate;
//增
@Test
public void testInsert(){
String sql = "insert into t_user values(null,?,?,?,?,?)";
jdbcTemplate.update(sql,"张三","1234","1","男","1@qq.com");
}
//删
@Test
public void testDelete(){
String sql = "delete from t_user where id = ?";
jdbcTemplate.update(sql,13);
}
//改
@Test
public void testUpdate(){
String sql = "update t_user set username = ? where id = ?";
jdbcTemplate.update(sql,"LL",16);
}
* 查询功能使用query方法实现
* 查询一条实体类对象
* 查询多条数据为一个list集合
* 查询单行单列的值
//查询一条实体类对象
@Test
public void testQuery(){
String sql = "select password from t_user where id = ?";
User user = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<>(User.class),7);
System.out.println(user);
}
//查询多条数据为一个list集合
@Test
public void testQueryList(){
String sql = "select * from t_user";
List<User> query = jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(User.class));
query.forEach(System.out::println);
}
//查询单行单列的值
@Test
public void testQueryConut(){
String sql = "select count(*) from t_user";
Integer integer = jdbcTemplate.queryForObject(sql, Integer.class);
System.out.println(integer);
}
}
5.声明式事务的概念
1.编程式事务
* 编程式事务:事务功能的相关操作全部通过自己编写代码来实现
* 存在问题:具体操作过程需要自己完成,繁琐;
* 每次代码实现功能都需要自己编写,代码复用性不高
2.声明式事务
学习了框架之后,就可以使用声明式方式,通过配置让框架实现功能。事务控制的代码比较有规律,代码的结构基本确定,所以框架可以将固定模式的代码抽取出来,进行相关的封装
* 提高开发效率
* 消除冗余代码
* 框架对健壮性、性能等进行各个方面的优化
6.基于注解的声明式事务(用这个)
1.配置事务
1. 在Spring的配置文件中配置事务管理器
2. 开启事务的注解驱动:可以将使用@Transactional注解所标识的方法或类中所有的方法使用事务进行管理
* transaction-manager属性:设置事务管理器的ID
3. 在service层中设置事务管理:加上@Transaction注解
* @Transaction标识在类上时,类中所有的方法都会被事务管理
注意:在mysql中,一个SQL独占一个事务,且自动提交。因此在实现事务时,必须先将自动提交关闭。
<!--
1.配置事务管理器
-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--
2.开启事务的注解驱动:
通过注解@Transactional所表示的方法或类中所有的方法,都会被事务管理器管理事务
-->
<tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserDao userDao;
@Transactional(
//只读
readOnly = true,
//超时
timeout = 3,
//回滚策略
noRollbackForClassName = "java.lang.IllegalArgumentException",
//隔离级别
isolation = Isolation.DEFAULT,
//传播行为
propagation = Propagation.REQUIRES_NEW
)
@Override
public void buyBook(Integer userId, Integer bookId) {
Integer price = userDao.getPriceByBookId(bookId);
}
}
7.基于xml的声明式事务(用得少)
1. 配置事务管理器
2. 配置事务通知
* 注意:基于xml实现声明式事务,必须引入aspectJ的依赖
8. 声明式事务的属性
1.只读
* 对一个查询操作,若将其设置为只读,就能告诉数据库该操作不涉及写操作。数据库就能针对查询操作进行优化
* 只有查询操作能用
* 设置readOnly=true
* 对增删改操作设置只读出现异常:SQLException
2.超时
* 事务在执行过程中,可能出现问题导致程序卡住,从而长时间占用数据库资源。此时这个出问题的程序应该被回滚,撤销已执行的操作,将资源让出来,让其他程序执行
* 超时回滚,释放资源
* 设置timeout=3;超时时间设置为3s,若3s未执行完,则强制回滚
3.回滚策略
* 可以在@Transaction设置回滚策略
* 声明式事务默认只针对运行时异常回滚,编译时异常不回滚;只要出现运行时异常都会回滚(不方便)
* 一般设置notrollbackFor、norollbackForClassName:不因为…而回滚
4.事务隔离级别
* 一个事务与其他事务隔离的程度称为隔离级别。SQL标准中规定了多种事务隔离级别,不同隔离级别对应不同的干扰程度,隔离级别越高,数据一致性就越好,但并发性越弱。
* 可在@Transaction中设置isolation(枚举类)属性
* 一共有四种隔离级别,按照隔离级别由低到高为:
* 读未提交
* 读已提交
* 可重复读(默认级别)
* 串行化
* 由于在mysql中可重复读不会出现幻读情况,因此可重复读为mysql的默认隔离级别
* 原因:在当前并发的两个可重复读的事务中,在每一个事务中都只能读到当前事务中的操作,mysql中的可重复读就能保证在一次事务中所能读取到的数据一定是当前事务中所操作之后的结果。从而解决幻读问题
5.事务传播行为
* 当事务方法被另一个事务方法调用时,必须指定事务应该如何传播
* 比如:方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行。
* 可以通过@Transaction中的propagation(枚举类)属性设置事务传播行为
* propagation=Propagation.REQUIRED,默认情况,表示如果当前线程上有已经开启的事务可用,那么就在这个事务中运行。
* propagation=Propagation.REQUIRES_NEW,表示不管当前线程上是否有已经开启的事务,都要开启新事务
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserDao userDao;
@Transactional(
//只读
readOnly = true,
//超时
timeout = 3,
//回滚策略
noRollbackForClassName = "java.lang.IllegalArgumentException",
//隔离级别
isolation = Isolation.DEFAULT,
//传播行为
propagation = Propagation.REQUIRES_NEW
)
@Override
public void buyBook(Integer userId, Integer bookId) {
Integer price = userDao.getPriceByBookId(bookId);
}
}