文章目录
Spring_JdbcTemplate
关注公众号“小东方不败”,码字不易!
Spring的学习主要分为3大块:IOC(包括DI),AOP,声明式事务。
本篇主要记录声明式事务的基础:【Spring框架本身】连接数据库相关的操作。
JdbcTemplate
和mybatis
很像,但是性能和SQL解耦方面还是有很大区别的。
Spring整合mybatis在技术实现上来说,是必要的。但是对于JdbcTemplate
的学习,作为学习Spring框架学习的补充,也是必要的。
0x01_JdbcTemplate介绍
JdbcTemplate
是spring
框架中提供的一个对象,是对原始繁琐的Jdbc API
对象的简单封装。spring框架为我们提供了很多的操作模板类。例如:操作关系型数据的JdbcTemplate
和操作nosql数据库的RedisTemplate
,操作消息队列的JmsTemplate
等等。
话不多说,直接上手试试。
0x02_创建项目
创建一个新的模块:
我目前用的IDEA版本是官网最新版。如果是2020.3版本的idea(之前我一直用这个版本,所以比较了解),只需要创建一个标准的maven项目即可,不需要选任何archetype。
创建项目完成之后,请务必确保下面的配置:
【1】maven:(最好是自己本地的maven)
【2】maven的JDK for importer
之前因为这个出现过不可描述的问题,所以每一次我都改成自己的JDK .
【3】在用到lombok的项目中需要勾选:Enable annotation processing
0x03_导入依赖
<dependencies>
<!-- Spring核心容器包-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.23</version>
</dependency>
<!-- Spring切面包-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.3.23</version>
</dependency>
<!-- druid数据源(连接池)-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.10</version>
</dependency>
<!-- spring aop联盟包 包含了针对面向切面的接口。 通常Spring等其它具备动态织入功能的框架依赖此包。-->
<dependency>
<groupId>aopalliance</groupId>
<artifactId>aopalliance</artifactId>
<version>1.0</version>
</dependency>
<!-- mysql驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.30</version>
</dependency>
<!-- SpringJDBC包-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.3.23</version>
</dependency>
<!-- spring映射依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>5.3.23</version>
</dependency>
<!-- apache commons日志包-->
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
<!-- Spring事务控制包tx-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.3.23</version>
</dependency>
<!-- junit测试的依赖-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
<!-- lombok 在实体类中使用注解,可以不用自己写get,set,toString,构造方法等方法-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.24</version>
<scope>provided</scope>
</dependency>
</dependencies>
注意:如果配置了阿里或者华为的镜像还是下载有问题的话,建议多install几次依赖,这个没办法。
0x04_resources目录下的配置文件
1.jdbc.properties
这个以前写过很多次,我直接拿来用了:
根据你的实际环境更改参数,比如数据库的账户名和密码,你的数据库名。
如果你的mysql版本不是8,那么下面url中的参数可能还需要你google进行更改。
jdbc_username=root
jdbc_password=root
jdbc_driver=com.mysql.cj.jdbc.Driver
jdbc_url=jdbc:mysql://127.0.0.1:3306/mydb?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
这个文件不要留多余的空格和换行。
2.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:p="http://www.springframework.org/schema/p"
xmlns:c="http://www.springframework.org/schema/c"
xmlns:util="http://www.springframework.org/schema/util"
xmlns:context="http://www.springframework.org/schema/context"
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/util
http://www.springframework.org/schema/util/spring-util.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
">
<!-- Spring注解扫描-->
<context:component-scan base-package="com.bones"/>
<!-- 读取properties的配置-->
<context:property-placeholder location="classpath:jdbc.properties"/>
<!-- 配置druid数据源(连接池)-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="url" value="${jdbc_url}"/>
<property name="username" value="${jdbc_username}"/>
<property name="password" value="${jdbc_password}"/>
<property name="driverClassName" value="${jdbc_driver}"/>
</bean>
<!-- 配置JdbcTemplate对象,并向里面注入DataSource对象(druid)-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<!-- 通过set方法注入druid数据源-->
<property name="dataSource" ref="dataSource"/>
</bean>
</beans>
注:
上面的最后是注入
JdbcTemplate
对象,其中需要配置druid
数据源(连接池),即配置初始化JdbcTemplate
对象时,需要的DataSource对象:什么意思呢?看一下
JdbcTemplate
类的一部分源码就可以理解了:初始化
JdbcTemplate
,会走构造方法,这点毋庸置疑吧:public JdbcTemplate(DataSource dataSource) { setDataSource(dataSource); afterPropertiesSet(); }
走构造方法的第一步就是设置数据源
setDataSource(dataSource);
,所以要将druid数据源设置为JdbcTemplate的数据源,则只需要调用set方法,所以就有了:<property name="dataSource" ref="dataSource"/>
0x05_搭建项目结构
数据库表的创建
由于sql文件比较大,可以去github下载:https://github.com/kirsten-1/sqlFile/blob/main/mydb.sql
实体类
com.bones.pojo.Emp
package com.bones.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.util.Date;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Emp implements Serializable {
private Integer empno;
private String ename;
private String job;
private Integer mgr;//上级的员工编号
private Date hiredate;
private Double sal;
private Double comm;//补助金
private Integer deptno;//部门编号
}
dao层
com.bones.dao.EmpDao
和com.bones.dao.impl.EmpDaoImpl
EmpDao
package com.bones.dao;
import org.springframework.stereoty
EmpDaoImpl
package com.bones.dao.impl;
import com.bones.dao.EmpDao;
import org.springframework.stereotype.Repository;
@Repository
public class EmpDaoImpl implements EmpDao {
}
service层
com.bones.service.EmpService
和com.bones.service.impl.EmpServiceImpl
EmpService
package com.bones.service;
public interface EmpService {
}
EmpServiceImpl
package com.bones.service.impl;
import com.bones.service.EmpService;
import org.springframework.stereotype.Service;
@Service
public class EmpServiceImpl implements EmpService {
}
目前的项目结构是 :
0x06_JdbcTemplate查询操作
实验1:查询返回值类型为int
EmpService
接口:
int findEmpcount();
EmpServiceImpl
实现类:
@Autowired
private EmpDao empDao;
@Override
public int findEmpcount() {
return empDao.findEmpcount();
}
EmpDao
接口:
int findEmpcount();
EmpDaoImpl
实现类
@Autowired
private JdbcTemplate jdbcTemplate;
@Override
public int findEmpcount() {
/** jdbcTemplate.queryForObject
* 查询的返回值类型是int数字类型
* 传入参数有2个
* 第一个参数:sql语句
* 第二个参数:返回值类型的class字节码文件
*/
String sql = "select count(1) from emp";
int count = jdbcTemplate.queryForObject(sql, Integer.class);
return count;
}
测试:
/**
* 对于JdbcTemplate的各种查询操作进行测试
*/
@Test
public void testJdbcTemplateQuery(){
//获取容器
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
EmpServiceImpl empService = context.getBean(EmpServiceImpl.class);
int empcount = empService.findEmpcount();
System.out.println("empcount = " + empcount);
}
测试结果:
注意JdbcTemplate的 API:
如果是查询结果的返回值类型是普通的基本数据类型:
int count = jdbcTemplate.queryForObject(sql, Integer.class);
- 第一个参数:查询的sql语句
- 第二个参数:返回值类型的class字节码文件
- 还要注意Autowired一个JdbcTemplate对象(已经在applicationContext.xml文件中注入了这个对象)
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <!-- 通过set方法注入druid数据源--> <property name="dataSource" ref="dataSource"/> </bean>
实验2:查询返回值类型是单个的实体类对象:
现在根据员工表的员工编号查询单个的emp:
EmpService
接口:
Emp findEmpByEmpno(int empno);
EmpServiceImpl
实现类:
@Autowired
private EmpDao empDao;
@Override
public Emp findEmpByEmpno(int empno) {
return empDao.findEmpByEmpno(empno);
}
EmpDao
接口:
Emp findEmpByEmpno(int empno);
EmpDaoImpl
实现类:
@Autowired
private JdbcTemplate jdbcTemplate;
@Override
public Emp findEmpByEmpno(int empno) {
/**
* 查询结果是单个的实体类对象 jdbcTemplate.queryForObject
* 第一个参数:SQL语句
* 第二个参数:RowMapper对象,因为RowMapper是一个接口,这里用Spring提供的RowMapper的实现类BeanPropertyRowMapper
* 需要定义泛型和实体类的字节码文件
* 第三个参数:SQL语句中的参数,一般是传进来的
*/
String sql = "select * from emp where empno = ?";//底层用的是preparedStatement
BeanPropertyRowMapper<Emp> mapper = new BeanPropertyRowMapper<>(Emp.class);
Emp emp = jdbcTemplate.queryForObject(sql, mapper, empno);
return emp;
}
测试:
@Test
public void testJdbcTemplateQuery1(){
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
EmpServiceImpl empService = context.getBean(EmpServiceImpl.class);
Emp emp = empService.findEmpByEmpno(7369);
System.out.println(emp);
}
测试结果:
注意:
关键的API
/** * 查询结果是单个的实体类对象 jdbcTemplate.queryForObject * 第一个参数:SQL语句 * 第二个参数:RowMapper对象,因为RowMapper是一个接口,这里用Spring提供的RowMapper的实现类BeanPropertyRowMapper * 需要定义泛型和实体类的字节码文件 * 第三个参数:SQL语句中的参数,一般是传进来的 */ String sql = "select * from emp where empno = ?";//底层用的是preparedStatement BeanPropertyRowMapper<Emp> mapper = new BeanPropertyRowMapper<>(Emp.class); Emp emp = jdbcTemplate.queryForObject(sql, mapper, empno);
其中
BeanPropertyRowMapper
是RowMapper
接口的一个实现类,是由spring提供的一个类,需要传入参数:查询结果封装的实体类的泛型和字节码文件。
实验3:查询返回值类型是List<T>
现在根据部门编号查询emp的List集合.
EmpService
接口:
List<Emp> findEmpsByDeptno(int deptno);
EmpServiceImpl
实现类
@Override
public List<Emp> findEmpsByDeptno(int deptno) {
return empDao.findEmpsByDeptno(deptno);
}
EmpDao
接口:
List<Emp> findEmpsByDeptno(int deptno);
EmpDaoImpl
实现类
@Override
public List<Emp> findEmpsByDeptno(int deptno) {
String sql = "select * from emp where deptno = ?";
BeanPropertyRowMapper<Emp> mapper = new BeanPropertyRowMapper<>(Emp.class);
List<Emp> emps = jdbcTemplate.query(sql, mapper, deptno);
return emps;
}
测试方法:
@Test
public void testJdbcTemplateQuery2(){
List<Emp> emps = empService.findEmpsByDeptno(10);
emps.forEach(System.out::println);
}
测试结果:
注意:
和查询单个的emp对象类似,也要用
RoeMapper
的对象:BeanPropertyRowMapper<Emp> mapper = new BeanPropertyRowMapper<>(Emp.class);
但是用的JdbcTemplate的API是:
List<Emp> emps = jdbcTemplate.query(sql, mapper, deptno);
- 第一个参数:SQL语句
- 第二个参数:RowMapper对象,这里用的是Spring提供的
RowMapper
实现类BeanPropertyRowMapper
,需要指定泛型和实体类的字节码文件。- 第三个参数SQL语句的需要传入的参数。
0x07_JdbcTemplate增删改操作
JdbcTemplate的增删改操作都是通过update方法完成的。
其实对于JdbcTemplate的学习建议可以CV代码,因为比较简单。
EmpService
接口:
int addEmp(Emp emp);
int updateEmp(Emp emp);
int deleteEmp(int empno);
EmpServiceImpl
实现类:
@Autowired
private EmpDao empDao;
@Override
public int addEmp(Emp emp) {
return empDao.addEmp(emp);
}
@Override
public int updateEmp(Emp emp) {
return empDao.updateEmp(emp);
}
@Override
public int deleteEmp(int empno) {
return empDao.deleteEmp(empno);
}
EmpDao
接口:
int addEmp(Emp emp);
int updateEmp(Emp emp);
int deleteEmp(int empno);
EmpDaoImpl
实现类:
@Override
public int addEmp(Emp emp) {
String sql = "insert into emp values(DEFAULT,?,?,?,?,?,?,?)";
Object[] args = {emp.getEname(),emp.getJob(),emp.getMgr(),emp.getHiredate(),emp.getSal(),emp.getComm(),emp.getDeptno()};
return jdbcTemplate.update(sql, args);
}
@Override
public int updateEmp(Emp emp) {
String sql = "update emp set ename = ?,job = ?,mgr = ?,hiredate = ?,sal = ?,comm = ?,deptno = ? where empno = ?";
Object[] args = {emp.getEname(),emp.getJob(),emp.getMgr(),emp.getHiredate(),emp.getSal(),emp.getComm(),emp.getDeptno(),emp.getEmpno()};
return jdbcTemplate.update(sql, args);
}
@Override
public int deleteEmp(int empno) {
String sql = "delete from emp where empno = ?";
return jdbcTemplate.update(sql,empno);
}
测试方法:
@Test
public void testAddEmp(){
int rows = empService.addEmp(new Emp(null, "Amy", "MANAGER", 7369, new Date(), 1900.0, 80.0, 10));
System.out.println("rows = " + rows);
}
@Test
public void testUpdateEmp(){
int rows = empService.updateEmp(new Emp(7941, "Jack", "SALSMAN", 7521, new Date(), 2000.0, 70.0, 20));
System.out.println("rows = " + rows);
}
@Test
public void testDeleteEmp(){
int rows = empService.deleteEmp(7941);
System.out.println("rows = " + rows);
}
0x08_JdbcTemplate_批操作
批操作主要是:批量增加,批量修改,批量删除。
下面的实验记录对dept表进行批量的增删改操作。
实验
DeptService
接口
package com.bones.service;
import com.bones.pojo.Dept;
import java.util.List;
public interface DeptService {
int[] batchAdd(List<Dept> depts);
int[] batchUpdate(List<Dept> depts);
int[] batchDelete(List<Integer> deptnos);
}
DeptServiceImpl
实现类
package com.bones.service.impl;
import com.bones.dao.DeptDao;
import com.bones.pojo.Dept;
import com.bones.service.DeptService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class DeptServiceImpl implements DeptService {
@Autowired
private DeptDao deptDao;
@Override
public int[] batchAdd(List<Dept> depts) {
return deptDao.batchAdd(depts);
}
@Override
public int[] batchUpdate(List<Dept> depts) {
return deptDao.batchUpdate(depts);
}
@Override
public int[] batchDelete(List<Integer> deptnos) {
return deptDao.batchDelete(deptnos);
}
}
DeptDao
接口
package com.bones.dao;
import com.bones.pojo.Dept;
import java.util.List;
public interface DeptDao {
int[] batchAdd(List<Dept> depts);
int[] batchUpdate(List<Dept> depts);
int[] batchDelete(List<Integer> deptnos);
}
DeptDaoImpl
实现类
package com.bones.dao.impl;
import com.bones.dao.DeptDao;
import com.bones.pojo.Dept;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
@Repository
public class DeptDaoImpl implements DeptDao {
@Autowired
private JdbcTemplate jdbcTemplate;
@Override
public int[] batchAdd(List<Dept> depts) {
String sql = "insert into dept values(DEFAULT,?,?)";
List<Object[]> args = new ArrayList<>();
for (Dept dept : depts) {
Object[] arg = {dept.getDname(),dept.getLoc()};
args.add(arg);
}
return jdbcTemplate.batchUpdate(sql,args);
}
@Override
public int[] batchUpdate(List<Dept> depts) {
String sql = "update dept set dname = ?,loc = ? where deptno = ?";
List<Object[]> args = new LinkedList<>();
for (Dept dept : depts) {
Object[] arg = {dept.getDname(),dept.getLoc(),dept.getDeptno()};
args.add(arg);
}
return jdbcTemplate.batchUpdate(sql, args);
}
@Override
public int[] batchDelete(List<Integer> deptnos) {
String sql = "delete from dept where deptno = ?";
List<Object[]> args = new ArrayList<>();
for (int deptno : deptnos) {
Object[] arg = {deptno};
args.add(arg);
}
return jdbcTemplate.batchUpdate(sql,args);
}
}
测试方法
package com.bones.test01;
import com.bones.pojo.Dept;
import com.bones.service.DeptService;
import com.bones.service.impl.DeptServiceImpl;
import com.bones.service.impl.EmpServiceImpl;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class Test2_TestJdbcTemplateBatch {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
DeptService deptService = context.getBean(DeptServiceImpl.class);
@Test
public void TestBatchAdd(){
List<Dept> depts = new ArrayList<>();
for (int i = 0; i < 5; i++) {
depts.add(new Dept(null,"dname"+i,"loc"+i));
}
int[] ints = deptService.batchAdd(depts);
System.out.println(Arrays.toString(ints));
}
@Test
public void testBatchUpdate(){
List<Dept> depts = new ArrayList<>();
for (int i = 44; i < 50; i++) {
depts.add(new Dept(i,"new_dname","new_loc"));
}
int[] ints = deptService.batchUpdate(depts);
System.out.println(Arrays.toString(ints));
}
@Test
public void testBatchDelete(){
List<Integer> empnos = new ArrayList<>();
for (int i = 44; i < 50; i++) {
empnos.add(i);
}
int[] ints = deptService.batchDelete(empnos);
System.out.println(Arrays.toString(ints));
}
}
总结
对于JdbcTemplate增删改的操作都是通过API
jdbcTemplate.batchUpdate(sql, args);
来完成的。看一下
jdbcTemplate.batchUpdate(sql, args);
的源码:@Override public int[] batchUpdate(String sql, List<Object[]> batchArgs) throws DataAccessException { return batchUpdate(sql, batchArgs, new int[0]); }
需要传入2个参数:
- 第一个参数:sql语句
- 第二个参数:
List<Object[]>
类型的对象,是对每一次操作的SQL语句中的参数封装成一个Object数组,数组中的每一个值都是SQL的?
占位的参数。因为是批量操作,所以是List类型的数组。
以上就是对JdbcTemplate对于数据库的基本操作。
0x09_JdbcTemplate总结
JdbcTemplate
是Spring JDBC
核心包(core
)中的核心类,它可以通过配置文件、注解、Java 配置类等形式获取数据库的相关信息,实现了对 JDBC 开发过程中的驱动加载、连接的开启和关闭、SQL 语句的创建与执行、异常处理、事务处理、数据类型转换等操作的封装。我们只要对其传入SQL 语句和必要的参数即可轻松进行 JDBC 编程。
- 1.Spring对数据库的操作在jdbc上面做了深层次的封装,使用spring的注入功能,可以把DataSource注册到JdbcTemplate之中。
这个就是在applicationContext.xml中配置的:
<!-- 配置JdbcTemplate对象,并向里面注入DataSource对象(druid)-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<!-- 通过set方法注入druid数据源-->
<property name="dataSource" ref="dataSource"/>
</bean>
这里对于JdbcTemplate的配置,我用的是Druid数据源(连接池)
- 2.JdbcTemplate主要提供以下方法:
query方法及queryForXXX(上面用的是
queryForObject
)方法:用于执行查询相关语句;update方法及batchUpdate方法:update方法用于执行新增、修改、删除等语句;batchUpdate方法用于执行批处理相关语句;
其他API:(作为了解)
方法 | 说明 |
---|---|
public int update(String sql) | 用于执行新增、更新、删除等语句;sql:需要执行的 SQL 语句;args 表示需要传入到 SQL 语句中的参数。 |
public int update(String sql,Object… args) | |
public void execute(String sql) | 可以执行任意 SQL,一般用于执行 DDL 语句; sql:需要执行的 SQL 语句;action 表示执行完 SQL 语句后,要调用的函数。 |
public T execute(String sql, PreparedStatementCallback action) | |
public List query(String sql, RowMapper rowMapper, @Nullable Object… args) | |
用于执行查询语句;sql:需要执行的 SQL 语句;rowMapper:用于确定返回的集合(List)的类型;args:表示需要传入到 SQL 语句的参数。 | |
public T queryForObject(String sql, RowMapper rowMapper, @Nullable Object… args) | |
public int[] batchUpdate(String sql, List<Object[]> batchArgs, final int[] argTypes) | 用于批量执行新增、更新、删除等语句; sql:需要执行的 SQL 语句;argTypes:需要注入的 SQL 参数的 JDBC 类型;batchArgs:表示需要传入到 SQL 语句的参数。 |
- 其他数据源:
<!--定义数据源 Bean-->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<!--数据库连接地址-->
<property name="url" value="${jdbc.url}"/>
<!--数据库的用户名-->
<property name="username" value="${jdbc.username}"/>
<!--数据库的密码-->
<property name="password" value="${jdbc.password}"/>
<!--数据库驱动-->
<property name="driverClassName" value="${jdbc.driver}"/>
</bean>
Spring 默认使用
DriverManagerDataSource
对数据库连接池进行管理,我们可以在 Spring 的 XML 配置文件中定义DriverManagerDataSource
的 Bean,并注入到 JdbcTempate 的 Bean 中。
建议使用druid 数据源。(就像我的例子)