目 录:
1. JdbcTemplate 简单概述
1.1 源码解析
1.2 相关方法说明
1.3 实战演练入门
1.3.1 基本类型测试
1.3.2 大文本对象测试
2. NamedParameterJdbcTemplate 简单概述
2.1 源码分析
2.2 实战演练入门
3. JdbcTemplate 的实现原理解密
3.1 自定义实现 JdbcTemplate
3.2 MyResultSetHandler 接口及实现(策略模式)
3.3 测试MyJdbcTemplate
Spring 对数据库的操作在 JDBC 上面做了基本的封装,让开发者在操作数据库时只需关注SQL语句和查询结果处理器,即可完成对数据库表相应的 CURD 功能(当然,只使用 JdbcTemplate,还不能摆脱持久层 DAO 实现类的编写)。
在配合 Spring 的 IoC 功能,可以把 DataSource 注册到 JdbcTemplate 之中。同时利用 Spring 基于 AOP 的事务即可完成简单的数据库 CRUD 操作。存在多数据源时,可以将不同的 DataSource 注册到 各自的 JdbcTemplate 中,Spring 实现不同对 JdbcTemplate 的 Bean 进行管理,从而实现多数据源操作数据库。
1.1 JdbcTemplate 的全限定名为 org.springframework.jdbc.core.JdbcTemplate
。对于 SpringBoot 项目要使用 JDBC 模板,只需引入 spring-boot-starter-jdbc 坐标,然后通过 @Autowired 注解完成自动注入 JdbcTemplate。其源码分析如下:
/**
* JdbcTemplate实现了JdbcOperations接口,操作方法都定义在此接口中
*/
public class JdbcTemplate extends JdbcAccessor implements JdbcOperations {
/**
* 使用默认构造函数构建JdbcTemplate
*/
public JdbcTemplate() {
}
/**
* 通过数据源构建JdbcTemplate
* @param dataSource
*/
public JdbcTemplate(DataSource dataSource) {
setDataSource(dataSource);
afterPropertiesSet();
}
/**
* 当使用默认构造函数构建时,提供了设置数据源的方法
* @param dataSource
*/
public void setDataSource(@Nullable DataSource dataSource) {
this.dataSource = dataSource;
}
}
1.2 JdbcTemplate 相关方法的说明如下:
execute 方法:
可以用于执行任何 SQL 语句,一般用于执行 DDL 语句;
update 方法及batchUpdate方法:
update 方法用于执行新增、修改、删除等语句;batchUpdate 方法用于执行批处理相关语句;
query 方法及 queryForXXX 方法:
用于执行查询相关语句;
call 方法:
用于执行存储过程、函数相关语句。
1.3 实战演练入门(分两部分)
以 SpringBoot 应用为例,参考《Spring Boot 快速入门系列(III)—— 数据操作篇之 JdbcTemplate》进行测试:
比如存在一个用户实体如下:
public class User implements Serializable {
// 主键
private String id;
// 姓名
private String name;
// 年龄
private String age;
// 性别
private String sex;
// 头像
private byte[] image;
// 个人简介
private String description;
/** 省略getter/setter */
}
1.3.1 (基本类型)测试类代码编写如下:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfiguration.class)
public class SpringJdbcTemplateTest {
@Autowired
private JdbcTemplate jdbcTemplate;
@Test
public void testInsert() {
jdbcTemplate.update("insert into user(name, age, sex) values (?,?,?)", "giserway", 18, "1");
}
@Test
public void testUpdate() {
jdbcTemplate.update("update user set name = ?, age = ? where id = ?", "tom", 20, 1);
}
@Test
public void testDelete() {
jdbcTemplate.update("delete from user where id = ?", 1);
}
@Test
public void testFindOne(){
User user = null;
try {
user = jdbcTemplate.queryForObject("select name, age, sex from user where id = ?", new BeanPropertyRowMapper<>(User.class),1);
} catch (Exception e) {
e.printStackTrace();
}
}
@Test
public void testFindAll() {
List<User> users = jdbcTemplate.query("select id,name,age,sex from user", new BeanPropertyRowMapper<>(User.class));
}
@Test
public void testFindCount() {
Long count = jdbcTemplate.queryForObject("select count(*) from user where age > ?", Long.class, 18);
}
@Test
public void testQueryForList() {
List<Map<String, Object>> list = jdbcTemplate.queryForList("select id,name,age,sex from user where age > ?", 18);
for (Map<String, Object> map : list) {
for (Map.Entry<String, Object> me : map.entrySet()) {
System.out.println(me.getKey() + "," + me.getValue());
}
}
}
@Test
public void testQueryForList2() {
List<String> list = jdbcTemplate.queryForList("select name from user where age > ?", String.class, 18);
for (String name : list) {
System.out.println(name);
}
}
@Test
public void testQueryForMap() {
Map<String, Object> map = jdbcTemplate.queryForMap("select id,name,age,sex from user where id = ?", 1);
for (Map.Entry me : map.entrySet()) {
System.out.println(me.getKey() + "," + me.getValue());
}
}
}
1.3.2 大文本对象测试类代码编写如下:
下面是通过 JdbcTemplate 实现大字段类型的存储,编码测试示例如下:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfiguration.class)
public class SpringJdbcTemplateCLOBAndBLOBTest {
@Autowired
private JdbcTemplate jdbcTemplate;
@Autowired
private LobHandler lobHandler;
@Test
public void testClobBlobWrite() {
try {
FileSystemResource res = new FileSystemResource("D:\\test\\1.jpg");
byte[] personImg = FileCopyUtils.copyToByteArray(res.getFile());
User user = new User();
user.setId(1);
user.setImage(personImg);
user.setDescription("本人性格热情开朗,待人友好,为人诚实谦虚。工作勤奋,认真负责,能吃苦耐劳,尽职尽责,有耐心。具有亲和力,平易近人,善于与人沟通,也许这天的我没什么值得推荐的荣誉,但是我有一颗简单的心,做好了应对困难的准备,期望您的机会和慧眼,相信我下次在做自我介绍时,会给您一个惊喜。");
jdbcTemplate.execute("update user set image = ?, description =? where id = ?", new AbstractLobCreatingPreparedStatementCallback(lobHandler) {
@Override
protected void setValues(PreparedStatement ps, LobCreator lobCreator) throws SQLException, DataAccessException {
lobCreator.setBlobAsBytes(ps, 1, user.getImage());
lobCreator.setClobAsString(ps, 2, user.getDescription());
ps.setInt(3, user.getId());
}
});
} catch (Exception e) {
e.printStackTrace();
}
}
@Test
public void testClobBlobRead() {
User user = jdbcTemplate.query("select id, name, image, description from user where id = ?", new ResultSetExtractor<User>() {
@Override
public User extractData(ResultSet rs) throws SQLException, DataAccessException {
User user = null;
if (rs.next()) {
user = new User();
user.setId(rs.getInt(1));
user.setName(rs.getString(2));
user.setImage(lobHandler.getBlobAsBytes(rs, 3));
user.setDescription(lobHandler.getClobAsString(rs, 4));
}
return user;
}
}, 3);
System.out.println(user);
}
}
表示,参数绑定受到位置的限制。定位参数的问题在于,一旦参数的顺序发生变化,就必须改变参数绑定。在 Spring JDBC 框架中,绑定 SQL 参数的另一种选择是使用具名参数(named parameter)。
具名参数:SQL 按名称(以冒号开头)而不是按位置进行指定。具名参数更易于维护,也提升了可读性。具名参数由框架类在运行时用占位符取代,具名参数只在 NamedParameterJdbcTemplate 中得到支持。NamedParameterJdbcTemplate 可以使用全部 jdbcTemplate 对象方法。
2.1 源码分析如下:
/**
* 通过观察源码我们发现,NamedParameterJdbcTemplate 里面封装了一个JdbcTemplate对象
* 只不过把它看成了接口类型JdbcOperations。
*/
public class NamedParameterJdbcTemplate implements NamedParameterJdbcOperations {
/**
* The JdbcTemplate we are wrapping.
*/
private final JdbcOperations classicJdbcTemplate;
/**
* 通过数据源构建 JdbcOperations
* @param dataSource
*/
public NamedParameterJdbcTemplate(DataSource dataSource) {
Assert.notNull(dataSource, "DataSource must not be null");
this.classicJdbcTemplate = new JdbcTemplate(dataSource);
}
/**
* 使用JdbcOperations 构建一个NamedParameterJdbcTemplate
* @param classicJdbcTemplate
*/
public NamedParameterJdbcTemplate(JdbcOperations classicJdbcTemplate) {
Assert.notNull(classicJdbcTemplate, "JdbcTemplate must not be null");
this.classicJdbcTemplate = classicJdbcTemplate;
}
//...
}
2.2 实战演练入门
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfiguration.class)
public class SpringJdbcTemplateUseTest {
@Autowired
private NamedParameterJdbcTemplate namedParameterJdbcTemplate;
@Autowired
private JdbcTemplate jdbcTemplate;
@Test
public void testFindMore() {
// 不知道有多少个id需要查询时,使用占位符?极其不方便
// List<User> users = jdbcTemplate.query("select name, age, sex from user where id in (?,?)",new Object[]{1,2},new BeanPropertyRowMapper<> (User.class));
Map<String, List<Integer>> map = new HashMap<>();
List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
map.put("ids", list);
List<User> users = namedParameterJdbcTemplate.query("select name, age, sex from user where id in(:ids)", map, new BeanPropertyRowMapper<>(User.class));
System.out.println(users);
}
@Test
public void testNamedParameter() {
User user = new User();
user.setName("jack");
user.setAge(18);
user.setSex("1");
BeanMap beanMap = BeanMap.create(user);
namedParameterJdbcTemplate.update("insert into user(name, age, sex) values (:name, :age, :sex)", beanMap);
}
}
3.1 自定义实现 MyJdbcTemplate
3.1.1 首先导入 pom 文件相关依赖
坐标如下:
<dependencies>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.9</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.45</version>
</dependency>
</dependencies>
3.1.2 自定义 MyJdbcTemplate
代码实现编码如下:
public class MyJdbcTemplate {
// 定义数据源
private DataSource dataSource;
// 通过构造函数给数据源赋值
public MyJdbcTemplate(DataSource dataSource) {
this.dataSource = dataSource;
}
//通过set方法给数据源赋值
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
/**
* 查询方法
* @param sql sql语句
* @param rsh 结果集处理器
* @param params sql语句的参数
* @return
*/
public Object query(String sql, MyResultSetHandler rsh, Object... params) {
//1.判断是否有数据源,没有数据源就直接抛异常
if (dataSource == null) {
throw new NullPointerException("DataSource can not empty!");
}
//2.定义连接和处理对象
Connection connection = null;
PreparedStatement pstm = null;
ResultSet rs = null;
try {
//2.获取连接
connection = dataSource.getConnection();
//3.获取预处理对象
pstm = connection.prepareStatement(sql);
//4.获取参数元信息
ParameterMetaData pmd = pstm.getParameterMetaData();
//5.获取参数个数
int parameterCount = pmd.getParameterCount();
//6.验证参数
if (parameterCount > 0) {
if (params == null) {
throw new NullPointerException("Parameter can not be null !");
}
if (parameterCount != params.length) {
throw new IllegalArgumentException("Incorrect parameter count: expected " + String.valueOf(parameterCount) + ", actual " + String.valueOf(params.length));
}
//7.给参数赋值
for (int i = 0; i < parameterCount; i++) {
pstm.setObject((i + 1), params[i]);
}
}
//8.验证通过,执行SQL语句
s = pstm.executeQuery();
//9.处理结果集:策略模式
return rsh.handle(rs);
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
release(connection, pstm, rs);
}
}
/**
* 增删改操作
* @param sql
* @param params
* @return
*/
public int update(String sql, Object... params) {
//1.判断是否有数据源,没有数据源就直接抛异常
if (dataSource == null) {
throw new NullPointerException("DataSource can not empty!");
}
//2.定义连接和处理对象
Connection connection = null;
PreparedStatement pstm = null;
try {
//2.获取连接
connection = dataSource.getConnection();
//3.获取预处理对象
pstm = connection.prepareStatement(sql);
//4.获取参数元信息
ParameterMetaData pmd = pstm.getParameterMetaData();
//5.获取参数个数
int parameterCount = pmd.getParameterCount();
//6.验证参数
if (parameterCount > 0) {
if (params == null) {
throw new NullPointerException("Parameter can not be null !");
}
if (parameterCount != params.length) {
throw new IllegalArgumentException("Incorrect parameter count: expected " + String.valueOf(parameterCount) + ", actual " + String.valueOf(params.length));
}
//7.给参数赋值
for (int i = 0; i < parameterCount; i++) {
pstm.setObject((i + 1), params[i]);
}
}
//8.验证通过,执行SQL语句
return pstm.executeUpdate();
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
release(connection, pstm, null);
}
}
/**
* 释放数据库连接
* @param conn
* @param pstm
* @param rs
*/
private void release(Connection conn, PreparedStatement pstm, ResultSet rs) {
if (rs != null) {
try {
rs.close();
} catch (Exception e) {
e.printStackTrace();
}
}
if (pstm != null) {
try {
pstm.close();
} catch (Exception e) {
}
if (conn != null) {
try {
conn.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
3.1.3 定义结果集处理接口 MyResultSetHandler
public interface MyResultSetHandler<T> {
/**
* 处理结果集
* @param rs
* @return
* @throws Exception
*/
Object handle(ResultSet rs) throws Exception;
}
接口两种实现方式如下(使用策略模式):
public class BeanHandler<T> implements ResultSetHandler {
private Class<T> requiredType;
private BeanListHandler<T> beanListHandler;
/**
* 覆盖默认无参构造
* @param requriedType
*/
public BeanHandler(Class requriedType) {
this.requiredType = requriedType;
}
public BeanHandler(BeanListHandler beanListHandler) {
this.beanListHandler = beanListHandler;
}
@Override
public T handle(ResultSet rs) throws Exception {
if (beanListHandler != null) {
return beanListHandler.handle(rs).get(0);
}
//1.定义返回值
T bean = null;
//2.由于是查询一个,所以只需判断rs能往下走,不用while循环即可
if (rs.next()) {
//3.实例化bean对象
bean = requiredType.newInstance();
// 4.获取参数元信息
ResultSetMetaData rsmd = rs.getMetaData();
//5.取出参数个数
int columnCount = rsmd.getColumnCount();
//6.遍历参数个数
for (int i = 0; i < columnCount; i++) {
//7.取出列名称
String columnLabel = rsmd.getColumnLabel(i + 1);
//8.取出列的值
Object value = rs.getObject(columnLabel);
//9.创建实体类的属性描述器,使用内省填充对象数据
PropertyDescriptor pd = new PropertyDescriptor(columnLabel, requiredType);
// 10. 获取属性的写方法
Method method = pd.getWriteMethod();
//11.填充数据
method.invoke(bean, value);
}
}
//返回
return bean;
}
}
public class BeanListHandler<T> implements ResultSetHandler {
private Class<T> requiredType;
/**
* 覆盖默认无参构造
* @param requriedType
*/
public BeanListHandler(Class requriedType) {
this.requiredType = requriedType;
}
@Override
public List<T> handle(ResultSet rs) throws Exception {
//1.定义返回值
List<T> list = new ArrayList();
T bean = null;
//2.由于是查询一个,所以只需判断rs能往下走,不用while循环即可
if (rs.next()) {
//3.实例化bean对象
bean = requiredType.newInstance();
//4.获取参数元信息
ResultSetMetaData rsmd = rs.getMetaData();
//5.取出参数个数
int columnCount = rsmd.getColumnCount();
//6.遍历参数个数
for (int i = 0; i < columnCount; i++) {
//7.取出列名称
String columnLabel = rsmd.getColumnLabel(i + 1);
//8.取出列的值
Object value = rs.getObject(columnLabel);
//9.创建实体类的属性描述器,使用内省填充对象数据
PropertyDescriptor pd = new PropertyDescriptor(columnLabel, requiredType);
//10.获取属性的写方法
Method method = pd.getWriteMethod();
//11.填充数据
method.invoke(bean, value);
}
//12.给list填充数据
list.add(bean);
}
//返回
return list;
}
}
3.2 测试自定义的 MyJdbcTemplate
3.2.1 新建配置类及自定义 Bean 对象的注入
/**
* 配置类
*/
@Configuration
@Import(JdbcConfig.class)
@PropertySource("classpath:jdbc.properties")
public class SpringConfiguration {
}
public class JdbcConfig {
@Value("${jdbc.driver}")
private String driver;
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
@Bean
public MyJdbcTemplate createJdbcTemplate(DataSource dataSource) {
return new MyJdbcTemplate(dataSource);
}
@Bean
public DataSource createDataSource() {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName(driver);
dataSource.setUrl(url);
dataSource.setUsername(username);
dataSource.setPassword(password);
return dataSource;
}
}
3.2.2 在 resources/ 下新建配置文件
jdbc.properties,内容如下:
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/db_test
jdbc.username=root
jdbc.password=123456
3.2.3 新建测试类
测试代码如下:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfiguration.class)
public class SpringJdbcTemplateUseTest {
@Autowired
private MyJdbcTemplate jdbcTemplate;
@Test
public void testInsert() {
jdbcTemplate.update("insert into user(name, age, sex) values (?,?,?)", "giserway", 18, "1");
}
@Test
public void testUpdate() {
jdbcTemplate.update("update user set name = ?, age = ? where id = ?", "tom", 20, 1);
}
@Test
public void testDelete() {
jdbcTemplate.update("delete from user where id = ?", 1);
}
@Test
public void testFindOne() {
User user = (User) jdbcTemplate.query("select name, age, sex from user where id = ?", new BeanHandler<>(User.class), 1);
System.out.println(user);
}
@Test
public void testFindList() {
List<User> users = (List<User>) jdbcTemplate.query("select name, age, sex from user where age > 18", new BeanListHandler<>(User.class));
for (User user : users) {
System.out.println(user);
}
}
}
4. 小结
本文从 JdbcTemplate 实现持久层入门到自定义实现,了解并掌握 JdbcTemplate 的基本使用及其实现原理;从自定义 JdbcTemplate 实现中,可以了解到策略模式的用法,策略模式是面向接口编程思想的具体体现,通常情况下,作为设计者会暴露出来一个接口,同时可以提供一些接口实现,也可以不提供,而让使用者根据具体情况去编写具体实现代码。以达到灵活的扩展目的。
加油