目录
- 十二.10、Spring之数据访问
- 十三、声明式事务
- 14、xml配置式事务声明
- 15、Spring整合Web
十二.10、Spring之数据访问
12.1、Spring数据访问工程环境搭建
导入需要的jar包
commons-logging-1.1.3.jar
druid-1.1.9.jar
mysql-connector-java-5.1.37-bin.jar
spring-aop-4.3.18.RELEASE.jar
spring-beans-4.3.18.RELEASE.jar
spring-context-4.3.18.RELEASE.jar
spring-core-4.3.18.RELEASE.jar
spring-expression-4.3.18.RELEASE.jar
spring-jdbc-4.3.18.RELEASE.jar
spring-orm-4.3.18.RELEASE.jar
spring-test-4.3.18.RELEASE.jar
spring-tx-4.3.18.RELEASE.jar
配置jdbc.properties
user=root
password=root
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/test
initialSize=5
maxActive=10
applicationContext.xml配置文件:
<?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: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 http://www.springframework.org/schema/context/spring-context-4.3.xsd">
<!-- 包扫描 -->
<context:component-scan base-package="com.atguigu"></context:component-scan>
<!-- 加载jdbc.properties属性配置文件 -->
<context:property-placeholder location="classpath:jdbc.properties"/>
<!-- 配置数据源 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="username" value="${user}" />
<property name="password" value="${password}" />
<property name="driverClassName" value="${driverClassName}" />
<property name="url" value="${url}" />
<property name="initialSize" value="${initialSize}" />
<property name="maxActive" value="${maxActive}" />
</bean>
</beans>
测试的代码:
@ContextConfiguration(locations="classpath:applicationContext.xml")
@RunWith(SpringJUnit4ClassRunner.class)
public class SpringTest {
@Autowired
DataSource dataSource;
@Test
public void testDataSource() throws Exception {
System.out.println( dataSource.getConnection() );
}
}
12.2、Spring之JdbcTemplate使用
在Spring中提供了对jdbc的封装类叫JdbcTemplate。它可以很方便的帮我们执行sql语句,操作数据库。
先准备单表的数据库数据
drop database if exists jdbctemplate;
create database jdbctemplate;
use jdbctemplate;
CREATE TABLE `employee` (
`id` int(11) primary key AUTO_INCREMENT,
`name` varchar(100) DEFAULT NULL,
`salary` decimal(11,2) DEFAULT NULL
);
insert into `employee`(`id`,`name`,`salary`)
values (1,'李三',5000.23),(2,'李四',4234.77),(3,'王五',9034.51),
(4,'赵六',8054.33),(5,'孔七',6039.11),(6,'曹八',7714.11);
select * from employee;
JdbcTemplate的使用需要在applicationContext.xml中进行配置
<!-- jdbcTemplate -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"/>
</bean>
实验2:将id=5的记录的salary字段更新为1300.00
@Test
public void test2() throws Exception {
String sql = "update employee set salary = ? where id = ?";
// update方法执行insret、update、delete语句
jdbcTemplate.update(sql,new BigDecimal(1300),5);
}
实验3:批量插入
@Test
public void test3() throws Exception {
String sql = "insert into employee(`name`,`salary`) values(?,?)";
/**
* 一条sql语句参数是一个一维数组,那么多条sql语句,它的参数是多个一维数组
*/
List<Object[]> batchArgs = new ArrayList<Object[]>();
batchArgs.add(new Object[] {"飞龙",new BigDecimal(99999)});
batchArgs.add(new Object[] {"英哥",new BigDecimal(9999)});
batchArgs.add(new Object[] {"国哥",new BigDecimal(999)});
jdbcTemplate.batchUpdate(sql, batchArgs);
}
实验4:查询id=5的数据库记录,封装为一个Java对象返回
创建Employee对象
public class Employee {
private Integer id;
private String name;
private BigDecimal salary;
测试代码:
@Test
public void test4() throws Exception {
String sql = "select id,name,salary from employee where id = ?";
/**
* 第一个参数是sql语句<br/>
* RowMapper接口,它的实现类可以帮我们把查询到的resultSet每一行记录封装成为javaBean返回
*/
Employee employee = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<Employee>(Employee.class), 5);
System.out.println(employee);
}
实验5:查询salary>4000的数据库记录,封装为List集合返回
@Test
public void test5() throws Exception {
String sql = "select id,name,salary from employee where salary > ?";
/**
* 查询一行记录使用queryForObject。<br/>
* 查询多行记录,使用query方法
*/
jdbcTemplate.query(sql, new BeanPropertyRowMapper<Employee>(Employee.class), new BigDecimal(4000))
.forEach(System.out::println);
}
实验6:查询最大salary
// 实验6:查询最大salary
@Test
public void test6() throws Exception {
String sql = "select max(salary) from employee";
BigDecimal salary = jdbcTemplate.queryForObject(sql, BigDecimal.class);
System.out.println( salary );
}
实验7:使用带有具名参数的SQL语句插入一条员工记录,并以Map形式传入参数值
配置applicationContext.xml配置文件:
<!-- 配置可以执行命名参数sql语句的 NamedParameterJdbcTemplate -->
<bean id="namedParameterJdbcTemplate"
class="org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate">
<constructor-arg index="0" ref="dataSource" />
</bean>
测试代码:
@Test
public void test7() throws Exception {
/**
* :name 相当于 ? 占位符,name就是这个参数的名称
*/
String sql = "insert into employee(`name`,`salary`) values(:name,:salary)";
Map<String, Object> paramMap = new HashMap<String, Object>();
paramMap.put("name", "我是命名(具名)参数的");
paramMap.put("salary", new BigDecimal(1234));
namedParameterJdbcTemplate.update(sql, paramMap);
}
实验8:重复实验7,以SqlParameterSource形式传入参数值
// 实验8:重复实验7,以SqlParameterSource形式传入参数值
@Test
public void test8() throws Exception {
/**
* :name 相当于 ? 占位符,name就是这个参数的名称
*/
String sql = "insert into employee(`name`,`salary`) values(:name,:salary)";
Employee employee = new Employee(null, "我是具名参数插入的", new BigDecimal(30000));
/**
* SqlParameterSource给sql语句传入需要的参数值
*/
namedParameterJdbcTemplate.update(sql, new BeanPropertySqlParameterSource(employee));
}
实验9:创建Dao,自动装配JdbcTemplate对象
创建EmployeeDao :
@Repository
public class EmployeeDao {
@Autowired
JdbcTemplate jdbcTemplate;
public int saveEmployee(Employee employee) {
return jdbcTemplate.update("insert into employee(`name`,`salary`) values(?,?)", employee.getName(),
employee.getSalary());
}
}
测试代码:
@Test
public void test9() throws Exception {
Employee employee = new Employee(null, "我是Dao插入的", new BigDecimal(1234));
employeeDao.saveEmployee(employee);
}
实验10:通过继承JdbcDaoSupport创建JdbcTemplate的Dao
@Repository
public class EmployeeDao extends JdbcDaoSupport{
// @Autowired
// JdbcTemplate jdbcTemplate;
@Autowired
public void initJdbcTemplate(DataSource dataSource) {
setDataSource(dataSource);
}
public int saveEmployee(Employee employee) {
return getJdbcTemplate().update("insert into employee(`name`,`salary`) values(?,?)", employee.getName(),
employee.getSalary());
}
}
测试代码:
@Test
public void test10() throws Exception {
Employee employee = new Employee(null, "我是JdbcDaoSupport插入的", new BigDecimal(1234));
employeeDao.saveEmployee(employee);
}
十三、声明式事务
事务分为声明式和编程式两种:
声明式事务:声明式事务是指通过注解的形式对事务的各种特性进行控制和管理。
编码式(编程式)事务:指的是通过编码的方式实现事务的声明。
13.0 事务回顾
13.0.0 事务的概念
事务:指逻辑上的一组操作,组成这组操作的各人但愿,要么全部成功,要么全部不成功。从而确保了数据的准确和安全。
下面以一个案例说明:
- 例如:A——B转帐,对应于如下两条sql语句:
/*转出账户减钱*/
update account set money=money-100 where name=‘a’;
/**转⼊账户加钱*/
update account set money=money+100 where name=‘b’;
13.0.2 事务的四大特性
- 原子性(Atomucity):原子性是指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。
这是 :从操作的角度来描述,事务中的各个操作要么都成功要么都失败。
- 一致性(Consistency):事务必须是数据库从一个一致性状态变换到另一个一致性状态。
例如:转账前A 有 1000 ,B有1000 。转账后A+B 也得是 2000.
这是:从数据的角度来说的,(1000,1000) (900,1100) ,不应该出现 ( 900,1000)的情况 )
- 隔离性(Isolation)事务的隔离性 是多个用户并发访问数据库时,数据库为每一个用户开启的事务,每个事务不能被其他事务的操作数据所干扰,多个并发事务之前要互相隔离。
比如:事务1给员工涨工资 2000,但是事务1尚未被提交,员工发起事务2查询工资,发现工资涨了2000块钱,读到了事务1尚未提交的数据(脏读)。 - 持久性(Durability):持久性是指一个事务一旦被提交,它对数据库中的数据的改变就是永久性的,接下来即使发生故障也不应该对齐有任何影响。
13.0.3 事务的隔离级别
不考虑隔离级别,会出现以下错误的情况:也即在隔离级别在解决事务并发问题
-
脏读: 一个事务读到另一个线程中未提交的数据
场景:
比如:事务1给员工涨工资 2000,但是事务1尚未被提交,员工发起事务2查询工资,发现工资涨了 2000块钱,读到了事务1尚未提交的数据(脏读)。 -
不可重复读:一个线程中 的事务读到了另一个线程中已经提交的update 的数据(前后内容不一样)
场景:
员工A发起事务1,查询工资,工资为1w ,此时 事务1尚未关闭。
财务人员发起了事务2,给员工A涨了 2000 块钱,并且提交了事务
员工A通过事务1再次发起查询请求,发现工资为1.2w,原来读出来1w 读不到了,叫做不可重复读。 -
虚读(幻读):一个线程中的事务读到了另一个线程中已经提交的insert或者delete的数据(前后条数不一样)
场景:
事务1、查询所有工资为1w 的员工的总数,查询出来了10个人,此时事务尚未关闭
事务2 、财务人员发起,新来员工,工资1w,向表中插入2条数据,并且提交了事务。
事务1、再次查询工资为1w 的员工个数,发现有 12个人。
13.0.4 数据库共定义了四种隔离级别
-
Serializable(串行化):
可避免脏读,不可重复读,虚读情况的发生。隔离最高
-
Repeatable read(可重复读):
可避免脏读,不可重复读情况的发生。(幻读有可能发生)隔离级别第二高
该机制下会对要update的行进行加锁,避免了不可重复读de 情况。 -
Read committed (读已提交) :可避免脏读情况发生。不可重复读和幻读一定会发生。
隔离级别第三
-
Read uncommitted(读为提交):最低级别,以上情况均无法保证。
隔离级别最低
注意:级别依次升⾼,效率依次降低
-
MySql 的默认隔离级别是:Repeatable read
-
查询当前使⽤的隔离级别:
select @@tx_isolation;
-
设置MySQL事务的隔离级别:
set session transaction isolation level xxx;
(设置的是当前
mysql连接会话的,并不是永久改变的)
13.1、编码方式实现事务
13.2、声明式事务环境搭建
13.2.1、准备测试数据库
##创建tx数据库
drop database if exists `tx`;
CREATE database `tx`;
##切换tx数据库
USE `tx`;
##删除用户表
DROP TABLE IF EXISTS `user`;
##创建用户表
CREATE TABLE `user` (
`id` int primary key auto_increment,
`username` varchar(50) NOT NULL,
`money` int(11) DEFAULT NULL
);
##插入数据
insert into `user`(`username`,`money`) values ('张三',1000),('李四',1000);
##删除图书表
drop table if exists `book`;
##创建图书表
create table `book`(
`id` int primary key auto_increment,
`name` varchar(500) not null,
`stock` int
);
##插入数据
insert into book(`name`,`stock`) values('java编程思想',100),('C++编程思想',100);
##查看数据
select * from book;
select * from user;
13.2.2、创建一个Java工程,导入Jar包
拷贝原来的Spring-jdbcTemplate工程
dao的代码
@Repository
public class BookDao {
@Autowired
private JdbcTemplate jdbcTemplate;
public void updateBook() {
jdbcTemplate.update("update book set name = '图书表被修改了'");
}
}
@Repository
public class UserDao {
@Autowired
JdbcTemplate jdbcTemplate;
public void updateUser() {
jdbcTemplate.update("update user set username='用户表被修改了'");
}
}
Service的代码:
@Service
public class TransactionService {
@Autowired
private UserDao userDao;
@Autowired
private BookDao bookDao;
public void multiUpdate() {
userDao.updateUser();
bookDao.updateBook();
}
}
13.3、测试Service的默认事务
正常情况下
public class SpringTest {
@Test
public void test1() throws Exception {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
TransactionService transactionService = (TransactionService) applicationContext.getBean("transactionService");
transactionService.multiUpdate();
}
}
- 异常的演示
修改TransactionService中的方法
最后的结果是:
用户表被修改了:
而图书表没有被修改:
Spring事务引入的分析------PlatformTransactionManager类简单介绍
PlatformTransactionManager接口是Spring提供用来管理事务的统一接口。
PlatformTransactionManager的实现类
Spring事务底层管理原理:
13.4、使用Spring的注解声明事务管制
实验2:测试Spring的声明式事务
给类或方法添加注解:
/**
* 表示此方法支持事务
*/
@Transactional
public void multiUpdate() {
userDao.updateUser();
int i = 12 / 0;
bookDao.updateBook();
}
applicationContext.xml中的配置:
<!-- 配置事务管理器
事务管理器id一般只叫:transactionManager。
-->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 这里的数据源一定是访问数据库时用的同一个数据源 -->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- tx:annotation-driven 标签表示支持@Transactional注解声明式事务
而且会自动代理
transaction-manager="transactionManager" 表示使用哪个事务管理器
如果事务管理器的id值是:transactionManager,此属性可以省略
-->
<tx:annotation-driven />
13.5、noRollbackFor和noRollbackForClassName测试不回滚的异常
实验3:noRollbackFor和noRollbackForClassName测试不回滚的异常
/**
* 表示此方法支持事务<br/>
* 默认情况下是RunTimeException运行时和它的子异常。会回滚事务<br/>
*
* noRollbackFor=ArithmeticException.class表示设置算术异常不回滚 <br/>
* noRollbackForClassName="java.lang.ArithmeticException" 设置指定全类名的异常不回滚事务
*/
@Transactional(noRollbackForClassName="java.lang.ArithmeticException")
public void multiUpdate() {
userDao.updateUser();
int i = 12 / 0;
bookDao.updateBook();
}
13.6、自定义设置回滚异常
实验5:rollbackFor和rollbackForClassName回滚的异常
/**
* 表示此方法支持事务<br/>
* 默认情况下是RunTimeException运行时和它的子异常。会回滚事务<br/>
* rollbackFor=FileNotFoundException.class 表示设置文件未找到异常也回滚事务<br/>
* rollbackForClassName="java.io.FileNotFoundException" 表示设置的全类名的异常回滚事务
*/
@Transactional(rollbackForClassName="java.io.FileNotFoundException")
public void multiUpdate() throws FileNotFoundException {
userDao.updateUser();
int i = 0;
if (i == 0) {
throw new FileNotFoundException();
}
bookDao.updateBook();
}
13.7、事务的只读属性
实验4:测试readOnly只读属性
/**
* 表示此方法支持事务<br/>
* 默认情况下是RunTimeException运行时和它的子异常。会回滚事务<br/>
* readOnly属性设置当前方法是否允许更新数据库
* 默认值是false,表示允许执行insert、delete、update语句
* readOnly=true,表示不允许执行insert、delete、update语句
*/
@Transactional(readOnly=true)
public void multiUpdate() throws FileNotFoundException {
userDao.updateUser();
// int i = 0;
// if (i == 0) {
// throw new FileNotFoundException();
// }
bookDao.updateBook();
}
报的异常
13.8、事务超时属性timeout(秒为单位)
/**
* 表示此方法支持事务<br/>
* 默认情况下是RunTimeException运行时和它的子异常。会回滚事务<br/>
* timeout=3 表示3秒后不允许再执行sql语句。
*/
@Transactional(timeout = 3)
public void multiUpdate() throws Exception {
userDao.updateUser();
Thread.sleep(4000);
bookDao.updateBook();
}
事务超时报如下异常:
13.10、事务的传播特性propagation
-
什么是事务的传播行为:
当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。例如:方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行。
事务的传播行为可以由传播属性指定。Spring定义了7种类传播行为。 -
事务的传播特性,有以下几种类型:
13.11、注解演示事物传播特性
UserService
BookService
TransactionService
实验1:大小事务传播特性都是REQUIRED
@Transactional(propagation = Propagation.REQUIRED)
public void multiTransaction() {
@Transactional(propagation = Propagation.REQUIRED)
public void updateBook() {
@Transactional(propagation=Propagation.REQUIRED)
public void updateUser() {
只要所有代码都没有异常。那么所有操作都将成功
只要有代码产生异常。那么所有操作都将失败。
实验2:大小事务传播特性都是REQUIRES_NEW
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void multiTransaction()
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void updateBook()
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void updateUser()
实验3:大事务是REQUIRED,小事务都是REQUIRES_NEW
@Transactional(propagation = Propagation.REQUIRED)
public void multiTransaction()
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void updateBook()
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void updateUser()
实验4:大事务是REQUIRED,小1REQUIRED,小2REQUIRES_NEW
@Transactional(propagation = Propagation.REQUIRED)
public void multiTransaction()
@Transactional(propagation = Propagation.REQUIRED)
public void updateBook()
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void updateUser()
14、xml配置式事务声明
1、拷贝刚刚的Spring-tx-annotation工程改为Spring-tx-xml(xml方式配置声明式事务)
2、记得去掉代码中所有@Transactional的注解。
<?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:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.3.xsd">
<!-- 包扫描 -->
<context:component-scan base-package="com.atguigu"></context:component-scan>
<!-- 加载jdbc.properties属性配置文件 -->
<context:property-placeholder location="classpath:jdbc.properties" />
<!-- 配置数据源 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="username" value="${user}" />
<property name="password" value="${password}" />
<property name="driverClassName" value="${driverClassName}" />
<property name="url" value="${url}" />
<property name="initialSize" value="${initialSize}" />
<property name="maxActive" value="${maxActive}" />
</bean>
<!-- 配置Spring提供用来访问数据库的类JdbcTemplate -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 事务由:AOP + 切面(事务管理器) + 事务属性 -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<!-- 配置事务属性 -->
<tx:advice id="tx_advice" transaction-manager="transactionManager">
<!-- 配置事务属性 -->
<tx:attributes>
<!--
方法在匹配事务属性规则的时候,匹配顺序是:精确匹配====>>>半模糊====>>>全模糊
-->
<!--
tx:method 标签给某些方法配置事务属性
name属性设置方法名
propagation="REQUIRED"表示有事务
精确匹配 -->
<tx:method name="updateUser" propagation="REQUIRED"/>
<!--
半模糊匹配
name="save*" 表示以save打头的方法,都匹配上
-->
<tx:method name="save*" propagation="REQUIRED"/>
<!--
半模糊匹配
name="update*" 表示以update打头的方法,都匹配上
-->
<tx:method name="update*" propagation="REQUIRES_NEW"/>
<!--
name="delete*" 表示以delete打头的方法,都匹配上
-->
<tx:method name="delete*" propagation="REQUIRED"/>
<tx:method name="multiUpdate" propagation="REQUIRES_NEW"/>
<tx:method name="multiTransaction" propagation="REQUIRED"/>
<!--
name="*" 表示剩下的方法
read-only="true" 底层会做一些事务优化
全模糊
-->
<tx:method name="*" read-only="true"/>
</tx:attributes>
</tx:advice>
<!-- 配置事务需要的aop(代理) -->
<aop:config>
<aop:advisor advice-ref="tx_advice" pointcut="execution(public * com.atguigu..*Service.*(..))"/>
</aop:config>
</beans>
15、Spring整合Web
15.1、在web工程中添加Spring的jar包。
- Spring的核心包
spring-beans-4.0.0.RELEASE.jar
spring-context-4.0.0.RELEASE.jar
spring-core-4.0.0.RELEASE.jar
spring-expression-4.0.0.RELEASE.jar - aop包
spring-aop-4.0.0.RELEASE.jar
spring-aspects-4.0.0.RELEASE.jar
com.springsource.org.aopalliance-1.0.0.jar
com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar - JDBC-ORM包
spring-jdbc-4.0.0.RELEASE.jar
spring-orm-4.0.0.RELEASE.jar
spring-tx-4.0.0.RELEASE.jar - Spring的web整合包
spring-web-4.0.0.RELEASE.jar - 测试包
spring-test-4.0.0.RELEASE.jar
整合Spring和Web容器分两个步骤:
1、导入spring-web-4.0.0.RELEASE.jar
2、在web.xml配置文件中配置org.springframework.web.context.ContextLoaderListener监听器监听ServletContext的初始化
3、在web.xml配置文件中配置contextConfigLocation上下文参数。配置Spring配置文件的位置,以用于初始化Spring容器
在web.xml中配置
获取WebApplicationContext上下文对象的方法如下:
方法一(推荐):
WebApplicationContextUtils.getWebApplicationContext(getServletContext())
方法二(不推荐):
getServletContext().getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);