基本介绍:通过 Spring 框架可以配置数据源,从而完成对数据表的操作。JdbcTemplate 是 Spring 提供的访问数据库的技术。将 JDBC 的常用操作封装为模板方法
1 JdbcTemplate 使用前需进行如下配置
<dependencies>
<!--加入c3p0数据源包-->
<dependency>
<groupId>com.mchange</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.5.2</version>
</dependency>
<!--加入mysql连接-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.31</version>
</dependency>
<!--加入spring新增的依赖 spring-jdbc,这个依赖中有JdbcTemplate-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.3.8</version>
</dependency>
<!--加入spring开发的基本包-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.8</version>
</dependency>
<!--加入spring开发切面编程需要的包-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.3.8</version>
</dependency>
</dependencies>
1.2 创建配置文件 resources/jdbc.properties
#配置用户名
jdbc.userName=root
#密码
jdbc.password=123456
jdbc.driverClass=com.mysql.cj.jdbc.Driver
#指定要连接的数据库,这里连接spring数据库
jdbc.url=jdbc:mysql://localhost:3306/spring
1.3 创建配置文件 resources/JdbcTemplate_ioc.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 https://www.springframework.org/schema/context/spring-context.xsd">
<!--引入外部的jdbc.properties配置文件-->
<context:property-placeholder location="classpath:jdbc.properties"/>
<!--配置数据源对象-DataSource-->
<bean class="com.mchange.v2.c3p0.ComboPooledDataSource" id="dataSource">
<property name="user" value="${jdbc.userName}"/>
<property name="password" value="${jdbc.password}"/>
<property name="driverClass" value="${jdbc.driverClass}"/>
<property name="jdbcUrl" value="${jdbc.url}"/>
</bean>
<!--配置JdbcTemplate对象-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<!--给jdbcTemplate对象配置dataSource属性-->
<property name="dataSource" ref="dataSource"/>
</bean>
</beans>
接下来就可以开始使用JdbcTemplate对象来操作数据库了!
2 JdbcTemplate对象常用操作
为成功演示以下操作,需提前进行如下准备
执行如下sql语句
-- 创建数据库
CREATE DATABASE spring;
USE spring
-- 创建表 monster
CREATE TABLE monster(
id INT PRIMARY KEY, `name` VARCHAR(64) NOT NULL DEFAULT '',
skill VARCHAR(64) NOT NULL DEFAULT '' )CHARSET=utf8mb4
INSERT INTO monster VALUES(100, '青牛怪', '吐火');
INSERT INTO monster VALUES(200, '黄袍怪', '吐烟');
INSERT INTO monster VALUES(300, '蜘蛛怪', '吐丝');
创建Monster.java
package com.spring.bean;
public class Monster {
private Integer monsterId;
private String name;
private String skill;
//全参构造器
public Monster(Integer monsterId, String name, String skill) {
this.monsterId = monsterId;
this.name = name;
this.skill = skill;
}
//无参构造器一定要写,Spring反射创建对象时,需要使用
public Monster() {
}
public Integer getMonsterId() {
return monsterId;
}
public void setMonsterId(Integer monsterId) {
this.monsterId = monsterId;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSkill() {
return skill;
}
public void setSkill(String skill) {
this.skill = skill;
}
@Override
public String toString() {
return "Monster{" +
"monsterId=" + monsterId +
", name='" + name + '\'' +
", skill='" + skill + '\'' +
'}';
}
}
2.1 添加单条数据
2.1.3 方式1:使用 void execute(final String sql) 方法
//测试通过JdbcTemplate对象完成添加数据
@Test
public void addDataByJdbcTemplate(){
//获取到容器
ApplicationContext ioc = new ClassPathXmlApplicationContext("jdbcTemplate_ioc.xml");
JdbcTemplate jdbcTemplate = ioc.getBean(JdbcTemplate.class);
//添加方式1
String sql = "INSERT INTO monster VALUES(400, '红孩儿', '枪法')";
jdbcTemplate.execute(sql);
}
2.1.3 方式2:使用 int update(String sql, @Nullable Object... args) 方法
//测试通过JdbcTemplate对象完成添加数据
@Test
public void addDataByJdbcTemplate(){
//获取到容器
ApplicationContext ioc = new ClassPathXmlApplicationContext("jdbcTemplate_ioc.xml");
JdbcTemplate jdbcTemplate = ioc.getBean(JdbcTemplate.class);
//添加方式2
String sql = "INSERT INTO monster VALUES(?, ?, ?)";
int affected = jdbcTemplate.update(sql, 500, "红孩儿2", "枪法2");
System.out.println("add ok affected = " + affected);
}
2.2 修改数据
方法:public int update(String sql, @Nullable Object... args)
//测试通过JdbcTemplate对象完成修改数据
@Test
public void updateDataByJdbcTemplate(){
//获取到容器
ApplicationContext ioc = new ClassPathXmlApplicationContext("JdbcTemplate_ioc.xml");
//获取JdbcTemplate对象
JdbcTemplate jdbcTemplate = ioc.getBean(JdbcTemplate.class);
//组织sql
String sql = "UPDATE monster SET skill = ? WHERE id = ?";
int affected = jdbcTemplate.update(sql, "美人计", 300);
System.out.println("update ok affected = " + affected);
}
2.3 批量添加数据
方法:public int[] batchUpdate(String sql, List<Object[]> batchArgs)
//测试通过JdbcTemplate对象完成批量添加数据
@Test
public void addBatchDataByJdbcTemplate(){
//获取到容器
ApplicationContext ioc = new ClassPathXmlApplicationContext("JdbcTemplate_ioc.xml");
//获取JdbcTemplate对象
JdbcTemplate jdbcTemplate = ioc.getBean(JdbcTemplate.class);
String sql = "INSERT INTO monster VALUES(?, ?, ?)";
List<Object[]> batchArgs = new ArrayList<>();
batchArgs.add(new Object[]{600, "红孩儿3", "枪法3"});
batchArgs.add(new Object[]{700, "红孩儿4", "枪法4"});
batchArgs.add(new Object[]{800, "红孩儿5", "枪法5"});
jdbcTemplate.batchUpdate(sql,batchArgs);
System.out.println("batch add ok...");
}
2.4 将单条查询数据封装到对象
方法:public <T> T queryForObject(String sql, RowMapper<T> rowMapper, Object... args)
//查询id=100的monster并封装到Monster实体对象
@Test
public void selectDataByJdbcTemplate() {
ApplicationContext ioc = new ClassPathXmlApplicationContext("JdbcTemplate_ioc.xml");
//得到JdbcTemplate bean
JdbcTemplate jdbcTemplate = ioc.getBean(JdbcTemplate.class);
//组织SQL
//通过BeanPropertyRowMapper获取,rowmapper 是一个接口,可以将查询的结果,封装到你指定的Monster对象中.
//1. 确定API : queryForObject()
//public <T> T queryForObject(String sql, RowMapper<T> rowMapper, @Nullable Object... args)
//2.准备参数
String sql = "SELECT id AS monsterId, `NAME`, skill FROM monster WHERE id = ?";
//使用RowMapper 接口来对返回的数据,进行一个封装-》底层使用的反射->setter
//这里有一个细节: 你查询的记录的表的字段需要和 Monster的对象字段名保持一致
RowMapper<Monster> rowMapper = new BeanPropertyRowMapper<>(Monster.class);
//jdbcTemplate
Monster monster = jdbcTemplate.queryForObject(sql, rowMapper, 100);
System.out.println("monster= " + monster);
System.out.println("查询ok");
}
2.5 封装多条查询数据
方法:public <T> T query(String sql, RowMapper<T> rowMapper, Object... args)
//查询id>=200的monster并封装到Monster实体对象
/**
* 查询多条记录
*/
@Test
public void selectMulDataByJdbcTemplate() {
ApplicationContext ioc =
new ClassPathXmlApplicationContext("JdbcTemplate_ioc.xml");
//得到JdbcTemplate bean
JdbcTemplate jdbcTemplate = ioc.getBean(JdbcTemplate.class);
//组织SQL
//通过BeanPropertyRowMapper获取rowmapper 是一个接口,可以将查询的结果,封装到你指定的Monster对象中.
//1. 确定API
//public <T> T query(String sql, RowMapper<T> rowMapper, Object... args){}
//2. 组织参数
String sql = "SELECT id AS monsterId, `NAME`, skill FROM monster WHERE id >= ?";
RowMapper<Monster> rowMapper = new BeanPropertyRowMapper<>(Monster.class);
//3. 调用
List<Monster> monsterList = jdbcTemplate.query(sql, rowMapper, 100);
for (Monster monster : monsterList) {
System.out.println("monster= " + monster);
}
}
2.6 查询返回结果只有一行一列的值
方法:public <T> T queryForObject(String sql, Class<T> requiredType)
//查询返回结果只有一行一列的值,比如查询id=100的怪物名
/**
* 查询返回结果只有一行一列的值
*/
@Test
public void selectScalarByJdbcTemplate() {
ApplicationContext ioc =
new ClassPathXmlApplicationContext("JdbcTemplate_ioc.xml");
//得到JdbcTemplate bean
JdbcTemplate jdbcTemplate = ioc.getBean(JdbcTemplate.class);
//1. 确定API
//public <T> T queryForObject(String sql, Class<T> requiredType)
//2. 提供参数
String sql = "SELECT NAME FROM monster WHERE id = 100";
//Class<T> requiredType 表示你返回的单行单列的数据类型
String name =
jdbcTemplate.queryForObject(sql, String.class);
System.out.println("返回name= " + name);
}
2.7 使用具名参数完成操作
在resources/JdbcTemplate_ioc.xml配置文件中增加如下配置
<!--配置NameParameterJdbcTemplate,支持具名参数-->
<bean class="org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate"
id="namedParameterJdbcTemplate">
<!--这里需要使用构造器关联数据源-->
<constructor-arg name="dataSource" ref="dataSource"/>
</bean>
方法:public int update(String sql, Map<String, ?> paramMap)
/**
* 使用Map传入具名参数完成操作,比如添加
*/
@Test
public void testDataByNamedParameterJdbcTemplate() {
ApplicationContext ioc =
new ClassPathXmlApplicationContext("JdbcTemplate_ioc.xml");
//得到NamedParameterJdbcTemplate bean
NamedParameterJdbcTemplate namedParameterJdbcTemplate =
ioc.getBean(NamedParameterJdbcTemplate.class);
//1. 确定使用API
//public int update(String sql, Map<String, ?> paramMap)
//2. 准备参数 [:my_id, :name, :skill] 要求按照规定的名字来设置参数
String sql = "INSERT INTO monster VALUES(:id, :name, :skill)";
Map<String, Object> paramMap = new HashMap<>();
//给paramMap填写数据
paramMap.put("id", 900);
paramMap.put("name", "蚂蚁精");
paramMap.put("skill", "喜欢打洞");
//3. 调用
int affected = namedParameterJdbcTemplate.update(sql, paramMap);
System.out.println("add ok affected=" + affected);
}
2.8 使用sqlparametersoruce 来封装具名参数
方法:public int update(String sql, SqlParameterSource paramSource)
//使用sqlparametersoruce 来封装具名参数,还是添加一个Monster 狐狸精
@Test
public void operDataBySqlparametersoruce() {
ApplicationContext ioc =
new ClassPathXmlApplicationContext("JdbcTemplate_ioc.xml");
//得到NamedParameterJdbcTemplate bean
NamedParameterJdbcTemplate namedParameterJdbcTemplate =
ioc.getBean(NamedParameterJdbcTemplate.class);
//确定API
//public int update(String sql, SqlParameterSource paramSource)
//public BeanPropertySqlParameterSource(Object object)
//准备参数
String sql = "INSERT INTO monster VALUES(:monsterId, :name, :skill)";
Monster monster = new Monster(1000, "大象精", "搬运木头");
SqlParameterSource sqlParameterSource =
new BeanPropertySqlParameterSource(monster);
//调用
int affected =
namedParameterJdbcTemplate.update(sql, sqlParameterSource);
System.out.println("add ok affected= " + affected);
}
2.9 Dao 对象中使用 JdbcTemplate 完成对数据的操作
创建 MonsterDao.java
package com.spring.dao;
import com.spring.bean.Monster;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
import javax.annotation.Resource;
public class MonsterDao {
private JdbcTemplate jdbcTemplate;
//完成保存任务
public void save(Monster monster){
//组织sql
String sql = "INSERT INTO monster VALUES(?,?,?)";
jdbcTemplate.update(sql, monster.getMonsterId(), monster.getName(), monster.getSkill());
}
public JdbcTemplate getJdbcTemplate() {
return jdbcTemplate;
}
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
}
在resources/JdbcTemplate_ioc.xml配置文件中增加如下配置
<!--配置monsterDao对象-->
<bean class="com.spring.dao.MonsterDao" id="monsterDao">
<property name="jdbcTemplate" ref="jdbcTemplate"/>
</bean>
编写测试代码:
//测试MonsterDAO
@Test
public void monsterDaoSave() {
ApplicationContext ioc =
new ClassPathXmlApplicationContext("JdbcTemplate_ioc.xml");
MonsterDao monsterDao = ioc.getBean(MonsterDao.class);
Monster monster = new Monster(1100, "小鸭精", "吃鱼");
monsterDao.save(monster);
System.out.println("MonsterDAO保存 ok ..");
}
3 声明式事务
3.1 声明式事务的基本使用
(1)在xml文件中加入以下配置
<!--配置事务管理器-对象
1. DataSourceTransactionManager 这个对象是进行事务管理-debug源码
2. 一定要配置数据源属性,这样指定该事务管理器 是对哪个数据源进行事务控制
-->
<bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager" id="transactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--配置启动基于注解的声明式事务管理功能-->
<tx:annotation-driven transaction-manager="transactionManager"/>
(2)在方法上添加注解@Transaction ,即可将该方法作为一个事务来处理(前提是该方法所在的类被注入到了容器中)
/**
* @Transactional 注解解读
* 1. 使用@Transactional 可以进行声明式事务控制
* 2. 即将标识的方法中的,对数据库的操作作为一个事务管理
* 3. @Transactional 底层使用的仍然是AOP机制
* 4. 底层是使用动态代理对象来调用buyGoodsByTx
* 5. 在执行buyGoodsByTx() 方法 先调用 事务管理器的 doBegin() , 调用 buyGoodsByTx()
* 如果执行没有发生异常,则调用 事务管理器的 doCommit(), 如果发生异常 调用事务管理器的 doRollback()进行回调
*/
@Transactional
public void buyGoodsByTx(int userId, int goodsId, int amount) {
//输出购买的相关信息
System.out.println("用户购买信息 userId=" + userId
+ " goodsId=" + goodsId + " 购买数量=" + amount);
//1.得到商品的价格
Float price = goodsDao.queryPriceById(userId);
//2. 减少用户的余额
goodsDao.updateBalance(userId, price * amount);
//3. 减少库存量
goodsDao.updateAmount(goodsId, amount);
System.out.println("用户购买成功~");
}
3.2 事务的传播机制
当有多个事务处理并存时,可以使用事务的传播机制去控制,比如用户去购买两次商品(使用不同的方法), 每个方法都是一个事务
(1)事务传播机制种类
- REQUIRED 和 REQUIRED_NEW 传播机制示意图
- 事务的传播机制的设置方法
- REQUIRES_NEW 和 REQUIRED 在处理事务的策略
3.3 事务隔离级别
3.3.1 事务隔离级别种类(事务相关知识点这篇有详细介绍mysql基础知识汇总)
3.3.2 事务隔离级别的设置
@Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.DEFAULT)
public void buyGoodsByTx2(int userId, int goodsId, int amount) {
在默认情况下 声明式事务的隔离级别是 REPEATABLE_READ . 我们将buyGoodsByTxISOLATION的隔离级别设置为 Isolation.READ_COMMITTED ,表示只要是提交的数据,在当前事务是可以读取到最新数据
@Transactional(isolation = Isolation.READ_COMMITTED)
public void buyGoodsByTxISOLATION() {
3.4 事务的超时回滚
介绍:如果一个事务执行的时间超过某个时间限制,就让该事务回滚。
设置方式如下:
timeout = 2 表示 buyGoods如果执行时间超过了2秒 , 该事务就进行回滚. 如果没有设置 timeout, 默认是 -1,表示使用事务的默认超时时间, 或者不支持