案例:
1.创建一个表book:
2.创建Book类,类中的属性用来对应book表的字段
public class Book {
private Integer id;
private String name;
private Double price;
......
}
3.编写Dao类
DAO实现CURD,只需要继承Spring提供 JdbcDAOSupport支持类 !
我们可以分析一波JdbcDAOSupport的源码,他内置了一个jdbcTemplate
而且只要注入datasource,就有了jdbcTemplate,相当于也注入了jdbcTemplate
编写Dao继承JdbcDaoSupport
//图书操作的dao层
//JdbcDaoSupport简化JdbcTemplate的代码开发。
public class BookDao extends JdbcDaoSupport {
//注入jdbctempate
// private JdbcTemplate jdbcTemplate;
// public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
// this.jdbcTemplate = jdbcTemplate;
// }
//保存图书
public void save(Book book){
String sql="insert into book values(null,?,?)";
//调用jdbctemplate
// jdbcTemplate.update(sql, book.getName(),book.getPrice());
super.getJdbcTemplate().update(sql, book.getName(),book.getPrice());
}
}
配置spring核心配置文件,注入jdbcTemplate到BookDao:
BookDao类继承JdbcDaoSupport,也就继承了setDataSource()方法,所以可以用
通过jdbcTemplate提供 update一个方法就可以
1.创建BookDao类
//图书操作的dao层
//JdbcDaoSupport简化JdbcTemplate的代码开发。
public class BookDao extends JdbcDaoSupport {
//注入jdbctempate
// private JdbcTemplate jdbcTemplate;
// public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
// this.jdbcTemplate = jdbcTemplate;
// }
//保存图书
public void save(Book book){
String sql="insert into book values(null,?,?)";
//调用jdbctemplate
// jdbcTemplate.update(sql, book.getName(),book.getPrice());
super.getJdbcTemplate().update(sql, book.getName(),book.getPrice());
}
//更新
public void update(Book book){
String sql="update book set name =? ,price =? where id =?";
super.getJdbcTemplate().update(sql, book.getName(),book.getPrice(),book.getId());
}
//删除
public void delete(Book book){
super.getJdbcTemplate().update("delete from book where id =?", book.getId());
}
}
增加、删除、修改功能都是通过update方法
查询单个对象
查询数量
编写BookDao类:
查询集合
手动装配对象和自动装配对象
//根据id查询
public Book findById(Integer id){
String sql ="select * from book where id = ?";
return super.getJdbcTemplate().queryForObject(sql,BeanPropertyRowMapper.newInstance(Book.class), id);
}
//查询所有
public List findAll( ){
String sql ="select * from book";
return super.getJdbcTemplate().query(sql,BeanPropertyRowMapper.newInstance(Book.class));
}
//条件查询: 根据图书名称模糊查询信息
public List findByNameLike(String name ){
String sql ="select * from book where name like ?";
return super.getJdbcTemplate().query(sql,BeanPropertyRowMapper.newInstance(Book.class),"%"+name+"%");
}
BeanPropertyRowMapper.newInstance(Book.class),就跟我们之前使用的BeanHandler一样,是来讲结果封装成对象的,之前Object和List是通过两个不同的Handler(BeanHandler或者BeanListHandler)
其中
Spring事务管理高层抽象主要包括3个接口,Spring的事务主要是由他们共同完成的:
三个核心接口:
该接口提供三个方法:
spring为不同的持久化框架提供了不同的PlatformTransactionManager接口实现:
事务 | 说明 |
---|---|
org.springframework.jdbc.datasource.DataSourceTransactionManager | 使用Spring JDBC或iBatis 进行持久化数据时使用 |
org.springframework.orm.hibernate5.HibernateTransactionManager | 使用Hibernate5.0版本进行持久化数据时使用 |
org.springframework.orm.jpa.JpaTransactionManager | 使用JPA进行持久化时使用 |
org.springframework.jdo.JdoTransactionManager | 当持久化机制是Jdo时使用 |
org.springframework.transaction.jta.JtaTransactionManager | 使用一个JTA实现来管理事务,在一个事务跨越多个资源时必须使用 |
事务管理器的选择?
用户根据选择和使用的持久层技术,来选择对应的事务管理器。
该接口主要提供的方法:
这些事务的定义信息,都可以在配置文件中配置和定制。
其中比较重要的是:事务的隔离级别和事务的传播行为
1.事务的隔离级别IsolationLevel
隔离级别 | 含义 |
---|---|
DEFAULT | 使用后端数据库默认的隔离级别(spring中的的选择项) |
READ_UNCOMMITED | 允许你读取还未提交的改变了的数据。可能导致脏、幻、不可重复读 |
READ_COMMITTED | 允许在并发事务已经提交后读取。可防止脏读,但幻读和 不可重复读仍可发生 |
REPEATABLE_READ | 对相同字段的多次读取是一致的,除非数据被事务本身改变。可防止脏、不可重复读,但幻读仍可能发生。 |
SERIALIZABLE | 完全服从ACID的隔离级别,确保不发生脏、幻、不可重复读。这在所有的隔离级别中是最慢的,它是典型的通过完全锁定在事务中涉及的数据表来完成的。 |
事务四大特性 ACID —隔离性引发问题 ---- 解决事务的隔离问题 隔离级别
Mysql 默认隔离级别 REPEATABLE_READ
Oracle 默认隔离级别 READ_COMMITTED
MySQL事务隔离级别以及验证
2.事务的传播行为PropagationBehavior
什么是事务的传播行为? 有什么作用?
事务传播行为用于解决两个被事务管理的方法互相调用问题
业务层两个方法面临的事务问题:
事务的传播行为的7种类型:
事务传播行为类型 | 说明 |
---|---|
PROPAGATION_REQUIRED | 支持当前事务,如果不存在 就新建一个 |
PROPAGATION_SUPPORTS | 支持当前事务,如果不存在,就不使用事务 |
PROPAGATION_MANDATORY | 支持当前事务,如果不存在,抛出异常 |
PROPAGATION_REQUIRES_NEW | 如果有事务存在,挂起当前事务,创建一个新的事务 |
PROPAGATION_NOT_SUPPORTED | 以非事务方式运行,如果有事务存在,挂起当前事务 |
PROPAGATION_NEVER | 以非事务方式运行,如果有事务存在,抛出异常 |
PROPAGATION_NESTED | 如果当前事务存在,则嵌套事务执行,只对DataSourceTransactionManager 起效 |
PROPAGATION_REQUIRED 默认,表示多个方法调用时,所有事务合并为同一个事务(默认)
前三个传播方法效果一样
PROPAGATION_REQUIRES_NEW 每个方法都有独立的事务
后三种传播方法效果一样
案例: 有A和B两个方法,都在各自的方法中开启了事务
//开启事务
A
//提交事务
//开启事务
B
//提交事务
主要分为三大类:
【面试题】REQUIRED、RE NESTED QUIRES_NEW、区分
REQUIRED:只有一个事务(默认,推荐)
REQUIRES_NEW:存在两个事务 ,如果事务存在,挂起事务,重新又开启了一个新的事务
NESTED 嵌套事务,事务可以设置保存点,回滚到保存点 ,选择提交或者回滚
事务运行过程中,每个时间点 事务状态信息 !
flush(),给hibernate使用,底层发出sql的
hasSavepoint():判断是否有保留点
isCompleted():判断事务是否结束
isNewTransaction():判断当前事务是否是新开的一个事务。
isRollbackOnly():判断事务是否只能回滚
setRollbackOnly():设置事务是否回滚
事务的结束:必须通过commit 确认事务提交, rollback 作用标记为回滚。
数据库操作中,如果只是回滚,后面不操作,数据库在关闭连接的时候,自动发出了commit。
try {
操作
} catch (){
rollback
} finally {
commit
}
【三个事务超级接口对象之间的关系】
1)首先用户管理事务,需要先配置TransactionManager(事务管理器)进行事务管理
2)然后根据TransactionDefinition(事务定义信息),通过TransactionManager(事务管理器)进行事务管理;
3)最后事务运行过程中,每个时刻都可以通过获取TransactionStatus(事务状态)来了解事务的运行状态。
Spring 支持两种方式事务管理
一:编程式的事务管理
通过TransactionTemplate手动管理事务
在实际应用中很少使用,原因是要修改原来的代码,加入事务管理代码
二:使用XML或注解配置声明式事务
Spring的声明式事务是通过AOP实现的(环绕通知)
代开发中经常使用(码侵入性最小)–推荐使用!
需求:账号转账,Tom账号取出1000元,存放到Jack账号上
脚本:
第一步:创建表t_account
CREATE TABLE `t_account` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`name` VARCHAR(20) NOT NULL,
`money` DOUBLE DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=INNODB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;
第二步:插入测试数据:
INSERT INTO `t_account` VALUES (1, 'Tom', 1000);
INSERT INTO `t_account` VALUES (2, 'Jack', 1100);
INSERT INTO `t_account` VALUES (3, 'Rose', 1200);
1.创建dao接口并实现
//接口
public interface IAccountDao {
//(存入)转入
public void in(String name,Double money);
//(取出)转出
public void out(String name,Double money);
}
//实现类
public class AccountDaoImpl extends JdbcDaoSupport implements IAccountDao{
@Override
public void in(String name, Double money) {
String sql="update t_account set money = money+ ? where name = ?";
super.getJdbcTemplate().update(sql, money,name);
}
@Override
public void out(String name, Double money) {
String sql="update t_account set money = money- ? where name = ?";
super.getJdbcTemplate().update(sql, money,name);
}
}
2.service层,创建IAccountService接口,编写转账的业务代码:
public interface IAccountService {
void transfer(String outName,String inName,Double money);
}
public class AccountServiceImpl implements IAccountService{
private IAccountDao accountDao;
//通过在xml中注入属性
public void setAccountDao(IAccountDao accountDao) {
this.accountDao = accountDao;
}
@Override
public void transfer(String outName, String inName, Double money) {
//取出钱
accountDao.out(outName, money);
//存入钱
accountDao.in(inName, money);
}
}
配置applicationContext.xml:
使用SpringTest进行测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations={"classpath:applicationContext.xml"})
public class SpringTest {
//注入测试的service
@Autowired
private IAccountService accountService;
//需求:账号转账,Tom账号取出1000元,存放到Jack账号上
@Test
public void testTransfer(){
accountService.transfer("Tom", "Jack", 1000d);
System.out.println("转账成功!");
}
}
但是发现问题:当我们在转账的过程中,如果发生了错误,就会导致一个账户取出了钱,而另一个账户却没有收到钱,这钱就不明不白失踪了
事务管理问题:在Service层没有事务的情况下,如果出现异常,则会转账不成功,数据异常。
扩展:如果不配置事务,那么每一个数据库的操作都是单独的一个事务。
操作思路:
1.确定目标:需要对AccountService 的transfer方法,配置切入点
2.需要Advice(环绕通知),方法前开启事务,方法后提交关闭事务
3.配置切面和切入点
第一步:导入aop相关的包(4个),引入约束名称空间(aop和tx 的名称空间)
配置Advice通知:
Spring为简化事务的配置,提供了
第一步:导入jar包:
其中:
com.springsource.org.aopalliance-1.0.0.jar:aop切面编程
com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar:注解开发切面
spring-aop-4.2.4.RELEASE.jar:aop切面编程
spring-aspects-4.2.4.RELEASE.jar:注解开发切面
spring-tx-4.2.4.RELEASE.jar:事务处理
配置spring容器,applicationContext.xml文件
之后我们再执行transfer方法,结果如下,表面事务管理奏效了
解读
表示:某个切面中的transfer方法开启了事务,事务的隔离级别是默认,传播行为是REQUIRED
我们之前要进行事务管理的前提是,两个方法的con必须一致,但是事务管理器是需要注入datasource(注入),所以在transfer中,只要是通过这个datasource获取的con,都能作用到,transfer执行ok,就commit,中间只要有个报错,就全部rollback
试验其他传播行为:
1.修改expression="bean(account*),此时切面是accountDao和accountService
2.需要在tx:advice中配置需要被事务管理的方法
注意点:
步骤:
1.在需要管理事务的方法或者类上面 添加@Transactional 注解
2.配置注解驱动事务管理(事务管理注解生效的作用)(需要配置对特定持久层框架使用的事务管理器)
1.配置:applicationContext-tx.xml
哪些要写在xml中
2.确定目标(bean的方法):
@Repository("accountDao")
public class AccountDaoImpl extends JdbcDaoSupport implements IAccountDao{
/*
* @Autowired原理是这样的,首先根据类型去spring中找对应的bean,作用在属性上时,会隐式生成setter方法注入
* @Autowired的另一个用处是可以作用于传参,原理一样
*/
@Autowired
public void setSuperDataSource(DataSource dataSource){
//调用父类的方法
super.setDataSource(dataSource);
}
@Override
public void in(String name, Double money) {
String sql="update t_account set money = money+ ? where name = ?";
super.getJdbcTemplate().update(sql, money,name);
}
@Override
public void out(String name, Double money) {
String sql="update t_account set money = money- ? where name = ?";
super.getJdbcTemplate().update(sql, money,name);
}
}
@Service("accountService")
@Transactional //会对该类中,所有共有的方法,自动加上事务
public class AccountServiceImpl implements IAccountService{
@Autowired
private IAccountDao accountDao;
public void setAccountDao(IAccountDao accountDao) {
this.accountDao = accountDao;
}
@Override
public void transfer(String outName, String inName, Double money) {
//取出钱
accountDao.out(outName, money);
int i = 1/0;
//存入钱
accountDao.in(inName, money);
}
@Transactional(readOnly=true)//使用局部覆盖全局的
public void findAccount(){
System.out.println("查询帐号的信息了");
}
}
注意:使用@Transactional注解,需要在核心文件中开启注解驱动
【扩展1】
如果 @Transactional 标注在 Class 上面, 那么将会对这个 Class 里面所有的 public 方法都包装事务方法。等同于该类的每个公有方法都放上了@Transactional。
如果某方法需要单独的事务定义,则需要在方法上加@Transactional来覆盖类上的标注声明。记住:方法级别的事务覆盖类级别的事务
//掌握操作的业务层
/**
* @Service("accountService")
* 相当于spring容器中定义:
*/
@Service("accountService")
@Transactional()//会对该类中,所有的共有的方法,自动加上事务--全局的设置,默认是可写
public class AccountServiceImpl implements IAccountService{
//注入dao
@Autowired
private IAccountDao accountDao;
//转账操作的业务逻辑
@Transactional(readOnly=false)//在方法上添加事务
public void transfer(String outName,String inName,Double money){
//调用dao层
//先取出
accountDao.out(outName, money);
int d = 1/0;
//再转入
accountDao.in(inName, money);
}
@Transactional(readOnly=true)//使用局部覆盖全局的
public void findAccount(){
System.out.println("查询帐号的信息了");
}
}
XML配置方式和注解配置方式 进行事务管理 哪种用的多?
XML方式,集中式维护,统一放置到applicationContext.xml文件中,缺点在于配置文件中的内容太多。
使用@Transactional注解进行事务管理,配置太分散,使用XML进行事务管理,属性集中配置,便于管理和维护
注意:以后的service的方法名字的命名,必须是上面规则,否则,不能被spring事务管理。!!!!
即以save开头的方法,update开头的方法,delete开头的方法,表示增删改的操作,故事务为可写
以find开头的方法,表示查询,故事务为只读
(1)xml方式小结
(2)注解方式小结
`