Spring系列
- Spring — Spring简介、入门、配置 , IoC和DI思想
- Spring — IoC核心(基于XML)、DI核心(基于XML)
- Spring — 使用IoC和DI模拟注册案例、注解配置IoC和DI
- Spring — 静态代理、动态代理、拦截器思想
- Spring — AOP思想、AOP开发、Pointcut语法、注解配置AOP
- Spring — DAO层、Spring JDBC、Spring事务控制
- Spring — XML配置事务、注解+XML、纯注解的配置方式
- Spring整合MyBatis
- Spring Java Config — 组件注册相关注解
- Spring Java Config — 常用注解
跳转到目录
很多持久层技术,单独使用的话,操作API会很麻烦, 有了Spring的支持,操作起来就会很简单; 更强大的是: Spring提供了对事务的支持
跳转到目录
Spring自身并没有提供持久层框架,但是提供了和持久层技术无缝整合的API;
跳转到目录
跳转到目录
跳转到目录
数据库
CREATE TABLE `employee` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`name` varchar(40) NOT NULL,
`age` int(11) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=13 DEFAULT CHARSET=utf8;
Java代码
// Dao包
public interface EmployeeDao1 {
void save(Employee1 emp);
void update(Employee1 emp);
void delete(Long id);
Employee1 get(Long id);
List<Employee1> listAll();
}
public class EmployeeDaoImpl1 implements EmployeeDao1 {
// =================核心===================
private JdbcTemplate jdbcTemplate;
// 属性: dataSource
public void setDataSourse(DataSource ds) {
this.jdbcTemplate = new JdbcTemplate(ds);
}
// =================核心===================
@SuppressWarnings("unchecked")
public void save(Employee1 emp) {
String sql = "INSERT INTO employee (name, age) VALUES (?, ?)";
jdbcTemplate.update(sql, emp.getName(), emp.getAge());
/*
// 包含了通过名称占位符的模板方法,简化开发;适合?比较多的情况
NamedParameterJdbcTemplate namedParameterJdbcTemplate = null;
namedParameterJdbcTemplate.update("INSERT INTO employee (name,age) VALUES (:ename,:eage)", new HashMap(){{
this.put("ename", emp.getName());
this.put("eage", emp.getAge());
}});*/
}
public void update(Employee1 emp) {
String sql = "UPDATE employee SET name = ?, age = ? WHERE id = ?";
jdbcTemplate.update(sql, emp.getName(), emp.getAge(), emp.getId());
}
public void delete(Long id) {
String sql = "DELETE from employee WHERE id = ?";
jdbcTemplate.update(sql, id);
}
public Employee1 get(Long id) {
List<Employee1> list = jdbcTemplate.query("SELECT id, name, age FROM employee WHERE id = ?", new Object[]{id},
new RowMapper<Employee1>() {
public Employee1 mapRow(ResultSet resultSet, int i) throws SQLException {
Employee1 emp = new Employee1();
emp.setId(resultSet.getLong("id"));
emp.setName(resultSet.getString("name"));
emp.setAge(resultSet.getInt("age"));
return emp;
}
});
return list.size() == 1 ? list.get(0) : null;
/*Employee1 employee1 = jdbcTemplate.queryForObject("SELECT id, name, age FROM employee WHERE id = ?",
new Object[]{id},new RowMapper() {
public Employee1 mapRow(ResultSet rs, int i) throws SQLException {
Employee1 emp = new Employee1();
emp.setId(rs.getLong("id"));
emp.setName(rs.getString("name"));
emp.setAge(rs.getInt("age"));
return emp;
}
});
return employee1;*/
}
public List<Employee1> listAll() {
return jdbcTemplate.query("SELECT id, name, age FROM employee", new RowMapper<Employee1>() {
// 把每一行的结果集映射成一个Employee对象
public Employee1 mapRow(ResultSet resultSet, int i) throws SQLException {
Employee1 emp = new Employee1();
emp.setId(resultSet.getLong("id"));
emp.setName(resultSet.getString("name"));
emp.setAge(resultSet.getInt("age"));
return emp;
}
});
}
}
xml
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<context:property-placeholder location="classpath:db.properties"/>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
init-method="init" destroy-method="close">
<property name="driverClassName" value="${jdbc.driverClassName}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
<property name="initialSize" value="${jdbc.initialSize}"/>
bean>
<bean id="employeeDaoImpl1" class="com.sunny.dao.impl.EmployeeDaoImpl1">
<property name="dataSourse" ref="dataSource"/>
bean>
beans>
测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class SpringJdbcTest {
@Autowired //表示将xml中创建的dao对象,注入到下面的变量中
private EmployeeDao1 dao;
@Test
public void testSave(){
Employee1 emp = new Employee1();
emp.setName("文");
emp.setAge(21);
dao.save(emp);
}
@Test
public void testUpdate(){
Employee1 emp = new Employee1();
emp.setName("西门吹风");
emp.setAge(43);
emp.setId(10L);
dao.update(emp);
}
@Test
public void testDelete(){
dao.delete(11L);
}
@Test
public void testGet(){
Employee1 employee1 = dao.get(10L);
System.out.println(employee1);
}
@Test
public void testListAll(){
List<Employee1> employee1s = dao.listAll();
for (Employee1 employee1 : employee1s) {
System.out.println(employee1);
}
}
}
通过查询操作,在代码中没有迭代结果集的操作;那么是谁在迭代结果集并且设置属性后,又是谁把创建出来的每一个Employee对象放到List集合中去的呢?
跳转到目录
JdbcDaoSupport类中定义了JdbcTemplate类并提供了get方法,我们dao层类直接继承即可,自己无需在声明JdbcTemplate对象;
public class EmployeeDaoImpl1ByJdbcTemplate extends JdbcDaoSupport implements EmployeeDao1 {
public void save(Employee1 emp) {
String sql = "INSERT INTO employee (name, age) VALUES (?, ?)";
super.getJdbcTemplate().update(sql, emp.getName(), emp.getAge());
}
xml
<bean id="employeeDaoImpl1ByJdbcTemplate" class="com.sunny.dao.impl.EmployeeDaoImpl1ByJdbcTemplate">
<property name="dataSource" ref="dataSource"/>
</bean>
Dao的bean元素依然要配置dataSource属性
跳转到目录
跳转到目录
银行转账案例: 需求: 从id为10086账户给id为10010账户转1000元
// domain包
@Data
public class Account {
private Long id;
private int balance;
}
// Dao包
public interface AccountDao {
/**
* 从指定账户转出多少钱
* @param outId
* @param money
*/
void transOut(Long outId, int money);
/**
* 从指定账户转入多少钱
* @param inId
* @param money
*/
void transIn(Long inId, int money);
}
public class AccountDaoImpl implements AccountDao {
private JdbcTemplate jdbcTemplate;
public void setDataSource(DataSource ds){
this.jdbcTemplate = new JdbcTemplate(ds);
}
public void transOut(Long outId, int money) {
String sql = "UPDATE account SET balance = balance - ? WHERE id = ?";
jdbcTemplate.update(sql, money, outId);
}
public void transIn(Long inId, int money) {
String sql = "UPDATE account SET balance = balance + ? WHERE id = ?";
jdbcTemplate.update(sql, money, inId);
}
}
// Service包
public interface AccountService {
/**
* 从指定账户转出指定金额给另一个账户
* @param outId
* @param inId
* @param money
*/
void trans(Long outId, Long inId, int money);
}
public class AccountServiceImpl implements AccountService {
private AccountDao dao;
public void setDao(AccountDao dao) {
this.dao = dao;
}
public void trans(Long outId, Long inId, int money) {
dao.transOut(outId, money);
int a = 1 / 0; // 抛出异常
dao.transIn(inId, money);
}
}
xml文件
<context:property-placeholder location="classpath:db.properties"/>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
init-method="init" destroy-method="close">
<property name="driverClassName" value="${jdbc.driverClassName}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
<property name="initialSize" value="${jdbc.initialSize}"/>
bean>
<property name="dataSource" ref="dataSource"/>
bean>
<bean id="accountService" class="com.sunny.service.impl.AccountServiceImpl">
<property name="dao" ref="accountDao"/>
bean>
测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class SpringTxTest {
@Autowired
private AccountService service;
@Test
public void test(){
service.trans(10086L, 10010L, 1000);
}
}
结果
分析原因:
出现问题的原因:
转入和转出的操作不在同一个事务之中;
跳转到目录
Spring的事务机制是Spring给我们提供的一套事务管理的方式,项目中我们就不需要手动去控制事务了
编程式事务:我们的事务控制逻辑(增强逻辑)和业务逻辑混合在一起,比如我们之前的tcf模式控制转账事务,这种方式就叫做编程式事务
声明式事务:通过配置,在不侵犯原有业务逻辑代码的基础上就添加了事务控制功能,这种方式叫做声明式事务(我们这里的Spring声明式事务控制就是通过AOP达到这个目的的)
事务(Transaction,简写为tx):在数据库中,事务是指一组逻辑操作,不论成功与失败都作为一个整体进行工作,要么全部执行成功,要么全部不执行(执行失败)。
事务基本特性(ACID,是针对单个事务的一个完美状态)
事务并发问题
脏读
财务人员今天心情不好,状态不好,误操作发起事务1给员工张三本月涨了1w块钱工资,但是还没有提交事务
张三发起事务2,查询当月工资,发现多了1W块钱,涨工资了,财务人员发现不对劲,把操作撤回,把涨工资的事务1给回滚了
幻读(幻读出现在增加insert和删除delete的时候)
不可重复读(出现在修改update的时候)
事务隔离级别(解决是事务并发问题的)
由低到高分别为 Read uncommitted > Read committed > Repeatable read > Serializable 。
Read_uncommited (读未提交),就好比十字路口没有红绿灯一样,效率高,但是风险也高,此时什么事务控制都没有。不要使用这种模式
Read_commited (读已提交),顾名思义,其他事务提交之后,才能读取到这个事务提交的数据,这种模式能解决脏读(因为脏读事务没提交造成的)问题,解决不了幻读和不可重复读(因为这两个问题的产生就是insert delete update的时候提交了事务造成)
Repeatable_Read (可重复读),可重复读解决脏读和不可重复读
Serializable (可序化):所有的事务一个个来,不争不抢,一个事务处理完了,另外一个事务继续进行,这样不会出现并发问题。比如ATM机
MySQL数据库默认隔离级别可重复读Repeatable_Read
Oracle数据库默认级别读已提交Read_commited
设置事务隔离级别
隔离级别越高,数据库事务并发执行性能会越差,在项目中为了考虑并发性能一般使用 Read committed(读已提交),它能避免丢失更新和脏读,但是不可重复读和幻读不可避免。更多情况下使用悲观锁或乐观锁来解决。
跳转到目录
Spring 支持编程式事务和声明式事务管理两种方式,这里学习声明式事务管理。
Spring 声明式事务管理建立在AOP
之上的。其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。
1、Spring 事务管理主要有3个接口:
TransactionDefinition接口:
在Spring中,事务是通过TransactionDefinition接口来定义的,该接口包含与事务属性相关的方法,TransactionDefinition定义了五个表示隔离级别的常量,代表传播行为的常量,在TransactionDefinition中以int值表示超时时间。
PlatformTransactionManager接口:
Platform TransactionManager.getInstance()方法返回一个Transaction Status对象,返回的Transaction Status对象可能代表一个新的或已经存在的事务(如果当前调用堆栈中有一个符合条件的事务)。
Transaction Status接口:
封装了一些控制事务查询和执行的方法。
2、使用Spring管理事务时,首先需要告诉Spring使用哪一个事务管理器
常用的事务管理器:
3、Spring 事务回滚规则
指示Spring 事务管理器回滚一个事务的推荐方法是在当前事务的上下文内抛出异常。spring事务管理器会捕捉任何未处理的异常,然后依据规则决定是否回滚抛出异常的事务。
默认配置下,spring只有在抛出的异常为运行时异常时才回滚该事务,也就是抛出的异常为RuntimeException的子类(Errors也会导致事务回滚),而抛出checked异常则不会导致事务回滚。可以明确的配置在抛出那些异常时回滚事务,包括checked异常。也可以明确定义那些异常抛出时不回滚事务。还可以编程性的通过setRollbackOnly()方法来指示一个事务必须回滚,在调用完setRollbackOnly()后你所能执行的唯一操作就是回滚。
跳转到目录
所谓事务的传播行为是指,如果在开始当前事务之前,一个事务上下文已经存在,此时有若干选项可以指定一个事务性方法的执行行为。在TransactionDefinition定义中包括了如下几个表示传播规则的常量:
情况一: 需要/遵从当前事务
TransactionDefinition.PROPAGATION_REQUIRED
:如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。这是默认值。情况二: 不遵从当前事务
TransactionDefinition.PROPAGATION_REQUIRES_NEW
:不管当前是否存在事务,都会新开启一个事务,必须是一个新的事务;情况三: 寄生事务
所谓事务超时,就是指一个事务所允许执行的最长时间,如果超过该时间限制但事务还没有完成,则自动回滚事务。在 TransactionDefinition 中以 int 的值来表示超时时间,其单位是秒。
1)本地事务和分布式事务
本地事务:就是普通事务,能保证单台数据库上操作的ACID,被限定在一台数据库上。
分布式事务:涉及多个数据库源的事务,即跨越多台同类或异类数据库的事务(由每台数据库的本地事务组成的),分布式事务旨在保证这些本地事务的所有操作的ACID,是事务可以跨越多台数据库。
2)JDBC事务和JTA事务
JDBC事务:就是数据库事务类型中的本地事务,通过 Connection对象的控制来管理事务。
JTA事务:JTA(Java Transaction API)是Java EE数据库事务规范,JTA只提供了事务管理接口,由应用程序服务器厂商提供实现,JTA事务比JDBC更强大,支持分布式事务。
3)通过编程实现事务可分为编程式事务和声明式事务
编程式事务:通过编写代码来管理事务
声明式事务:通过注解或XML配置来管理事务