Spring-04

Spring-04

Spring之数据访问

1.Spring数据访问工程环境搭建

先导入需要的jar包

druid-1.1.9.jar

junit_4.12.jar

mysql-connector-java-5.1.37-bin.jar

org.hamcrest.core_1.3.0.jar

spring-aop-5.2.5.RELEASE.jar

spring-aspects-5.2.5.RELEASE.jar

spring-beans-5.2.5.RELEASE.jar

spring-context-5.2.5.RELEASE.jar

spring-core-5.2.5.RELEASE.jar

spring-expression-5.2.5.RELEASE.jar

spring-jcl-5.2.5.RELEASE.jar

spring-jdbc-5.2.5.RELEASE.jar

spring-orm-5.2.5.RELEASE.jar

spring-test-5.2.5.RELEASE.jar

spring-tx-5.2.5.RELEASE.jar

创建jdbc.properties属性配置文件

1
2
3
4
5
6
7
8
user=root
password=root
url=jdbc:mysql://localhost:3306/jdbcTemplate

driverClassName=com.mysql.jdbc.Driver
initialSize=5
maxActive=10

创建applicationContext.xml配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?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 https://www.springframework.org/schema/context/spring-context.xsd">


<!--
加载jdbc.properties属性配置文件
-->
<context:property-placeholder location="classpath:jdbc.properties" />

<!-- 配置数据库连接池 -->
<bean class="com.alibaba.druid.pool.DruidDataSource" id="dataSource">
<property name="username" value="${user}" />
<property name="password" value="${password}" />
<property name="url" value="${url}" />
<property name="driverClassName" value="${driverClassName}" />
<property name="initialSize" value="${initialSize}" />
<property name="maxActive" value="${maxActive}" />
</bean>


</beans>

2.Spring之JdbcTemplate使用

在Spring中提供了对jdbc的封装类叫JdbcTemplate。它可以很方便的帮我们执行sql语句,操作数据库。

先准备单表的数据库数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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中进行配置

1
2
3
4
5
<!-- jdbcTemplate -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"/>
</bean>

实验2:将id=5的记录的salary字段更新为1300.00

1
2
3
4
5
6
7
8
9
10
@Test
public void test2() {
String sql = "update employee set salary=? where id=?";
/**
* update() 方法执行insert , delete ,update的sql语句 <br/>
* 返回值影响的行数
*/
System.out.println(jdbcTemplate.update(sql, new BigDecimal("1300.00"), 5));
}

实验3:批量插入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
 实验3:批量插入
@Test
public void test3() {

String sql = "insert into employee(`name`,`salary`) values(?,?)";
/**
* update() 方法执行insert , delete ,update的sql语句 <br/>
* 返回值影响的行数 <br/>插入一条
*/
// System.out.println(jdbcTemplate.update(sql, "老王,你真行!",new BigDecimal("1300.00")));

List<Object[]> batchArgs = new ArrayList<Object[]>();
batchArgs.add(new Object[]{"沙比特",new BigDecimal(1000)});
batchArgs.add(new Object[]{"邱比特",new BigDecimal(2000)});
batchArgs.add(new Object[]{"沙马特",new BigDecimal(3000)});

jdbcTemplate.batchUpdate(sql,batchArgs);

}

实验4:查询id=5的数据库记录,封装为一个Java对象返回

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

@Test
public void test4() {

String sql = "select `id`,`name`,`salary` from employee where id = ?";
/**
* queryForObject() 用于查询返回一行的sql <br/>
* 第一个参数是sql语句<br/>
* 第二个参数是RowMapper接口实现类。这个接口负责将查询回来的结果转换为需要的数据( 比如Employee对象 )<br/>
* 第三个参数是sql语句中占位符的值
*/
Employee employee = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<Employee>(Employee.class), 5);

System.out.println(employee);
}

实验5:查询salary>4000的数据库记录,封装为List集合返回

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@Test
public void test5() {
String sql = "select `id`,`name`,`salary` from employee where salary > ?";
/**
* BeanPropertyRowMapper类负责将查询回来的每一行记录转换为一个具体的Bean对象 <br/>
*
* JdbcTemplate工具类跟DbUtils中的QueryRunner接近<br/>
* update() 都是执行insert,delete,update的sql语句<br/>
*
* 在JdbcTemplate中,由方法决定查询一条记录,还是多条记录<br/>
* queryforObject()查询一条记录<br/>
* query() 查询多条记录
* 在DbUtils中,由ResultSetHandler实现类决定查询一条记录,还是多条记录<br/>
* 一条记录是 BeanHandler() <br/>
* 多条记录是 BeanListHandler()
*/
List<Employee> query = jdbcTemplate.query(sql, new BeanPropertyRowMapper<Employee>(Employee.class), new BigDecimal(4000));

for (Employee employee : query) {
System.out.println(
employee
);
}
}

实验6:查询最大salary

1
2
3
4
5
6
7
8
9
10
@Test
public void test6() {
String sql = "select max(salary) from employee";
// 第一个参数是sql语句
// 第二个参数是转换回去的类型
BigDecimal bigDecimal = jdbcTemplate.queryForObject(sql, BigDecimal.class);

System.out.println(bigDecimal);
}

实验7:使用带有具名参数的SQL语句插入一条员工记录,并以Map形式传入参数值

applicationContext.xml配置文件:

1
2
3
4
5
6
<!-- 配置可执行具名|命名参数的sql的jdbcTemplate -->
<bean class="org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate"
id="namedParameterJdbcTemplate">
<constructor-arg index="0" ref="dataSource" />
</bean>

测试的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

@Test
public void test7() {
/**
* :name 表示占位符?,参数数名是name <br/>
* :salary 表示占位符?,参数数名是salary <br/>
* 具名|命名参数的格式是 ==>> :参数名 <br/>
*/
String sql = "insert into employee(`name`,`salary`) values( :name , :salary )";

Map<String,Object> paramMap = new HashMap<>();
paramMap.put("name","爱你,么么哒!!!");
paramMap.put("salary",new BigDecimal(90000));

namedParameterJdbcTemplate.update(sql,paramMap);

}


实验8:重复实验7,以SqlParameterSource形式传入参数值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

@Test
public void test8() {
/**
* :name 表示占位符?,参数数名是name <br/>
* :salary 表示占位符?,参数数名是salary <br/>
* 具名|命名参数的格式是 ==>> :参数名 <br/>
*/
String sql = "insert into employee(`name`,`salary`) values( :name , :salary )";

Employee employee = new Employee(null,"国哥,万万岁",new BigDecimal(9999));

namedParameterJdbcTemplate.update(sql, new BeanPropertySqlParameterSource(employee));

}

实验9:创建Dao,自动装配JdbcTemplate对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package com.atguigu.test;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;

import java.util.List;

@Repository
public class EmployeeDao {

@Autowired
JdbcTemplate jdbcTemplate;

public List<Employee> queryAll(){
String sql = "select `id`,`name`,`salary` from employee";

List<Employee> query = jdbcTemplate.query(sql, new BeanPropertyRowMapper<Employee>(Employee.class));

return query;
}

}

在applicationContext.xml中添加包扫描:

<context:component-scan base-package="com"></context:component-scan>

测试代码:

1
2
3
4
5
6
7
8
@Autowired
EmployeeDao employeeDao;

@Test
public void test9() {
employeeDao.queryAll().forEach(System.out::println);
}

实验10:通过继承JdbcDaoSupport创建JdbcTemplate的Dao

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Repository
public class EmployeeDao extends JdbcDaoSupport {

// @Autowired
// JdbcTemplate jdbcTemplate;

@Autowired
public void initDataSource(DataSource dataSource){
setDataSource(dataSource);
}

public List<Employee> queryAll(){
String sql = "select `id`,`name`,`salary` from employee";

List<Employee> query = getJdbcTemplate().query(sql, new BeanPropertyRowMapper<Employee>(Employee.class));

return query;
}

}

声明式事务

事务分为声明式和编程式两种:

声明式事务:声明式事务是指通过注解或xml配置的形式对事务的各种特性进行控制和管理。

编码式(编程式)事务:指的是通过编码的方式实现事务的声明。

1、编码方式实现事务:

2、声明式事务环境搭建

2.1、准备测试数据库

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
##创建tx数据库
drop database if exists `tx`;
CREATE database `tx`;
##切换tx数据库
USE `tx`;

##创建用户表
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);

##创建图书表
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;

2.2、创建一个Java工程,导入Jar包

UserDao代码:

1
2
3
4
5
6
7
8
9
10
11
12
@Repository
public class UserDao {

@Autowired
JdbcTemplate jdbcTemplate;

public int updateUser(){
return jdbcTemplate.update("update `user` set `username` = ?","用户表被修改");
}

}

BookDao代码:

1
2
3
4
5
6
7
8
9
10
@Repository
public class BookDao {

@Autowired
JdbcTemplate jdbcTemplate;

public int updateBook(){
return jdbcTemplate.update("update `book` set `name` = ?","图书表被修改了");
}
}

Service代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Service
public class TransactionService {

@Autowired
BookDao bookDao;

@Autowired
UserDao userDao;

public void multiUpdate(){
bookDao.updateBook();
userDao.updateUser();
}

}

测试代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
@ContextConfiguration(locations = "classpath:applicationContext.xml")
@RunWith(SpringJUnit4ClassRunner.class)
public class TransactionTest {

@Autowired
TransactionService transactionService;

@Test
public void test() {
transactionService.multiUpdate();
}

}

修改jdbc.properties属性配置文件:

1
2
3
4
5
6
user=root
password=root
url=jdbc:mysql://localhost:3306/tx
driverClassName=com.mysql.jdbc.Driver
initialSize=5
maxActive=10

3、测试Service的默认事务

实验1:测试service服务层的默认事务

1
2
3
4
5
6
public void multiUpdate(){
bookDao.updateBook();
int i = 12 / 0;
userDao.updateUser();
}

异常的演示

这时候抛出除以0的异常。并且数据库的图书表被修改,但是用户表没改

Spring事务引入的分析——PlatformTransactionManager类简单介绍

PlatformTransactionManager接口:

事务管理器的实现类( 针对不同持久层技术有不同的事务管理器实现类 )

而我们重点关心的是DataSourceTransactionManager,因为我们底层使用的了DataSource数据源访问操作数据库

Spring底层事务管理原理:

4、使用Spring的注解声明事务管制

实验2:测试Spring的声明式事务

Spring声明式事务管理只需要以下三步配置:

1 配置事务管理器DataSourceTransactionManager到Spring容器中

1
2
3
4
5
6
7
8
9
<!--
配置事务管理器
-->
<bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager"
id="transactionManager">
<!-- 这里的数据源,一定是我们访问数据库使用的数据源,同一个 -->
<property name="dataSource" ref="dataSource" />
</bean>

2 配置事务的AOP

1
2
3
4
5
6
7
8
9
10
11
<!--
给需要添加事务的类加上代理
配置使用哪个事务管理器
transaction-manager="transactionManager"
默认情况下。如果事务管理器的id值为transactionManager。则此属性可以省略不配置
因为transaction-manager属性的默认值就是:transactionManager

tx:annotation-driven标签跟注解@Transactional一起组合使用
-->
<tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>

3 给需要使用事务的方法是添加注解@Transactional

5、noRollbackFor和noRollbackForClassName测试不回滚的异常

Spring底层默认是对运行时异常RuntimeException,以及运行时子异常进行回滚事务

1
2
3
4
5
6
7
8
9
10
11
12

/**
* @Transactional 注解表示当前方法使用事务 <br/>
* noRollbackFor 属性指定哪些异常不回滚事务<br/>
* noRollbackForClassName 属性指定哪些全类名的异常不回滚<br/>
*/
@Transactional(noRollbackForClassName = "java.lang.ArithmeticException")
public void multiUpdate(){
bookDao.updateBook();
int i = 12 / 0;
userDao.updateUser();
}

6、自定义设置回滚异常

实验5:rollbackFor和rollbackForClassName回滚的异常

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

/**
* @Transactional 注解表示当前方法使用事务 <br/>
* rollbackFor 指定哪些异常滚回事务<br/>
* rollbackForClassName 指定哪些全类名的异常回滚事务 <br/>
*/
@Transactional(rollbackForClassName = "java.io.FileNotFoundException")
public void multiUpdate() throws FileNotFoundException {
bookDao.updateBook();
int i = 12 ;
if (i == 12) {
throw new FileNotFoundException("我就是找不到,你打我呀");
}
userDao.updateUser();
}


7、事务的只读属性

只读,就是只允许执行查询操作,不允许执行写操作( insert, delete ,update ).

默认情况下,都是非只读事务

实验4:测试readOnly只读属性

1
2
3
4
5
6
7
8
9
10
/**
* @Transactional 注解表示当前方法使用事务 <br/>
readOnly = true 表示当前方法不允许执行写操作
*/
@Transactional(readOnly = true)
public void multiUpdate() throws FileNotFoundException {
bookDao.updateBook();
userDao.updateUser();
}

只读事务。写异常

8、事务超时属性timeout(秒为单位)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* @Transactional 注解表示当前方法使用事务 <br/>
*
* timeout = 3 表示3秒后不允许再执行任何语句,
*/
@Transactional(timeout = 3)
public void multiUpdate() {
bookDao.updateBook();
try {
Thread.sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
userDao.updateUser();
}

10、事务的传播特性propagation

什么是事务的传播行为:

  • 当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。

    例如:方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行。

  • 事务的传播行为可以由传播属性指定。Spring定义了7种类传播行为。

事务的传播特性,有以下几种类型:

11、注解演示事物传播特性

UserService

BookService

TransactionService

实验1:大小事务传播特性都是REQUIRED

1
2
3
4
5
6
7
@Transactional(propagation = Propagation.REQUIRED)
public void multiTransaction() {
@Transactional(propagation = Propagation.REQUIRED)
public void updateBook() {
@Transactional(propagation=Propagation.REQUIRED)
public void updateUser() {

实验2:大小事务传播特性都是REQUIRES_NEW

1
2
3
4
5
6
7
@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,小1REQUIRED,小2REQUIRES_NEW

1
2
3
4
5
6
7
@Transactional(propagation = Propagation.REQUIRED)
public void multiTransaction()
@Transactional(propagation = Propagation.REQUIRED)
public void updateBook()
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void updateUser()

数据库事务隔离级别

数据库隔离级别是为了解决数据库并发过程中数据的安全问题而存在的四种隔离级别。

四种事务隔离级别:

  • 一:读未提交 read uncommitted

  • 二:读已提交 read committed Oracle默认隔离级别

  • 三:可重复读 repeatable read MySQL默认隔离级别

  • 四:串行事务 serializable

  • 由事务隔离级别产生的几个常见问题:

​ 读未提交,可导致—–>>>> 脏读

​ 读已提交,可导致—–>>>> 不可重复读

​ 重复读,可导致 —–>>>> 幻读

什么是脏读?

​ 一个事务读到另一个事务的写操作数据,但是这些数据还没提交生效。

什么是不可重复读?

​ 两次以上一模一样的查询得到的结果不一样。

什么是幻读?

​ 在A事务中读取多次相同的一张表的数据,但是B事务在这个过程中做了添加或删除的写操作。

​ A事务多次读取的结果并没有得到更新( 读不到删除和添加 )。

1.查看当前会话隔离级别

1
select @@tx_isolation;

2.查看系统当前隔离级别

1
select @@global.tx_isolation;

3.设置当前会话隔离级别

1
2
3
4
set session transaction isolation level read uncommitted;		读未提交
set session transaction isolation level read committed; 读已提交
set session transaction isolation level repeatable read; 可重复读
set session transaction isolation level serializable; 串行化事务(读写不能并发)

4.开启事务

1
start transaction;

5.提交事务

1
commit;

6.回滚事务

1
rollback;

1.脏读的演示:

2、读未提交导致不可重复读:

3、可重复读导致幻读的演示

4、串行化事务的演示

xml配置式事务声明

(1) 新建一个模块

(2) 拷贝原来所有的jar包和代码。以及配置文件

(3) 去掉。所有@Transactional的注解。

(4) 在ApplicationContext.xml中添加如下的配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
<!-- 1.事务管理器 DataSourceTransactionManager -->
<bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager"
id="transactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>

<!--2.自定义事务属性细节-->
<tx:advice id="tx_advice" transaction-manager="transactionManager">
<!-- tx:attributes 自定义事务属性 -->
<tx:attributes>
<!--
tx:method 为某个|某些方法配置事务属性
name="updateBook" 方法名( 精确匹配 | 半模糊匹配 | 全模糊匹配 )
-->
<tx:method name="updateBook" propagation="REQUIRED"/>
<tx:method name="updateUser" propagation="REQUIRES_NEW"/>
<tx:method name="multiTransaction" propagation="REQUIRED"/>
<tx:method name="multiUpdate" propagation="REQUIRED"/>
<!--
tx:method name="save*" 它表示以save字符串打头的方法
它属于半模糊匹配。
-->
<tx:method name="save*" propagation="REQUIRED" />
<tx:method name="insert*" propagation="REQUIRED" />
<tx:method name="update*" propagation="REQUIRED" />
<tx:method name="delete*" propagation="REQUIRED" />
<!--
tx:method name="*" 表示前面匹配完后,剩下的所有方法
它的全模糊匹配
read-only="true" 底层会做一些优化。
以上三种匹配规则,优先顺序是 : 精确匹配 => 半模糊匹配 => 全模糊匹配
-->
<tx:method name="*" read-only="true" />
</tx:attributes>
</tx:advice>

<!--
3.代理 AOP (切入点表达式)
-->
<aop:config>
<aop:advisor advice-ref="tx_advice"
pointcut="execution(public * com.atguigu..*Service*.*(..))"
/>
</aop:config>

Spring整合Web

1 先创建Spring IOC 容器对象

2 从Spring IOC 容器对象中获取组件对象

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和Web容器分两个步骤:

1、导入spring-web-4.0.0.RELEASE.jar

2、在web.xml配置文件中配置org.springframework.web.context.ContextLoaderListener监听器监听ServletContext的初始化

3、在web.xml配置文件中配置contextConfigLocation上下文参数。配置Spring配置文件的位置,以用于初始化Spring容器

2.在web.xml中配置

1
2
3
4
5
6
7
8
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

获取WebApplicationContext上下文对象的方法如下

方法一(推荐):

1
WebApplicationContextUtils.getWebApplicationContext(getServletContext())

方法二(不推荐):

1
getServletContext().getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);

Spring-04
http://www.kju147.com/2021/07/20/Spring-04/
作者
个人学习
发布于
2021年7月20日
许可协议