JDBC 基础
Java程序使用JDBC接口访问关系数据库的时候,需要以下几步:
- 创建全局DataSource实例,表示数据库连接池;
- 在需要读写数据库的方法内部,按如下步骤访问数据库:
- 从全局DataSource实例获取Connection实例;
- 通过Connection实例创建PreparedStatement实例;
- 执行SQL语句,如果是查询,则通过ResultSet读取结果集,如果是修改,则获得int结果。
正确编写JDBC代码的关键是使用try ... finally
释放资源,涉及到事务的代码需要正确提交或回滚事务。
在Spring使用JDBC,首先我们通过IoC容器创建并管理一个DataSource实例
,然后,Spring提供了一个JdbcTemplate
,可以方便地让我们操作JDBC,因此,通常情况下,我们会实例化一个JdbcTemplate
。顾名思义,这个类主要使用了Template模式
。
编写示例代码或者测试代码时,我们强烈推荐使用HSQLDB
这个数据库,它是一个用Java编写的关系数据库,可以以内存模式或者文件模式运行,本身只有一个jar包,非常适合演示代码或者测试代码,本文使用MySql
我们以实际工程为例,先创建Maven工程spring-data-jdbc
,然后引入以下依赖:
1 | java复制代码<dependencies> |
在AppConfig
中,我们需要创建以下几个必须的Bean:
1 | java复制代码@Configuration |
在上述配置中:
- 通过
@PropertySource("jdbc.properties")
读取数据库配置文件; - 通过
@Value("${jdbc.url}")
注入配置文件的相关配置; - 创建一个DataSource实例,它的实际类型是
HikariDataSource
,创建时需要用到注入的配置; - 创建一个JdbcTemplate实例,它需要注入
DataSource
,这是通过方法参数完成注入的。
最后,针对HSQLDB写一个配置文件jdbc.properties
:
1 | java复制代码# 数据库文件名为testdb: |
可以通过HSQLDB自带的工具来初始化数据库表,这里我们写一个Bean,在Spring容器启动时自动创建一个users表:
1 | java复制代码@Component |
现在,所有准备工作都已完毕。我们只需要在需要访问数据库的Bean中,注入JdbcTemplate
即可:
1 | java复制代码@Component |
JdbcTemplate 用法
Spring提供的JdbcTemplate
采用Template模式
,提供了一系列以回调为特点的工具方法,目的是避免繁琐的try...catch
语句。
我们以具体的示例来说明JdbcTemplate
的用法。
首先我们看T execute(ConnectionCallback<T> action)方法
,它提供了Jdbc的Connection
供我们使用:
1 | java复制代码public User getUserById(long id) { |
也就是说,上述回调方法允许获取Connection,然后做任何基于Connection的操作。
我们再看T execute(String sql, PreparedStatementCallback<'T'> action)
的用法:
1 | java复制代码public User getUserByName(String name) { |
最后,我们看T queryForObject(String sql, Object[] args, RowMapper<'T'> rowMapper)方法
:
1 | java复制代码public User getUserByEmail(String email) { |
RowMapper定义:
1 | java复制代码@FunctionalInterface |
方法getUserByEmail中(ResultSet rs, int rowNum) -> {}
语句实现一个RowMapper继承类的对象
在queryForObject()方法
中,传入SQL以及SQL参数后,JdbcTemplate
会自动创建PreparedStatement
,自动执行查询并返回ResultSet
,我们提供的RowMapper
需要做的事情就是把ResultSet
的当前行映射成一个JavaBean
并返回。整个过程中,使用Connection
、PreparedStatement
和ResultSet
都不需要我们手动管理。
RowMapper
不一定返回JavaBean
,实际上它可以返回任何Java对象。例如,使用SELECT COUNT(*)
查询时,可以返回Long:
1 | java复制代码public long getUsers() { |
如果我们期望返回多行记录,而不是一行,可以用query()方法:
1 | java复制代码public List<User> getUsers(int pageIndex) { |
上述query()方法
传入的参数仍然是SQL、SQL参数以及RowMapper实例。这里我们直接使用Spring提供的BeanPropertyRowMapper
。如果数据库表的结构恰好和JavaBean的属性名称一致,那么BeanPropertyRowMapper
就可以直接把一行记录按列名转换为JavaBean。
如果我们执行的不是查询,而是插入、更新和删除操作,那么需要使用update()方法:
1 | java复制代码public void updateUser(User user) { |
只有一种INSERT操作比较特殊,那就是如果某一列是自增列(例如自增主键),通常,我们需要获取插入后的自增值。JdbcTemplate
提供了一个KeyHolder
来简化这一操作:
1 | java复制代码public User register(String email, String password, String name) { |
JdbcTemplate
还有许多重载方法,这里我们不一一介绍。需要强调的是,JdbcTemplate
只是对JDBC操作的一个简单封装,它的目的是尽量减少手动编写try(resource) {...}
的代码,对于查询,主要通过RowMapper
实现了JDBC结果集到Java对象的转换。
我们总结一下JdbcTemplate
的用法,那就是:
- 针对简单查询,优选query()和queryForObject(),因为只需提供SQL语句、参数和RowMapper;
- 针对更新操作,优选update(),因为只需提供SQL语句和参数;
- 任何复杂的操作,最终也可以通过execute(ConnectionCallback)实现,因为拿到Connection就可以做任何JDBC操作。
实际上我们使用最多的仍然是各种查询。如果在设计表结构的时候,能够和JavaBean的属性一一对应,那么直接使用BeanPropertyRowMapper
就很方便。如果表结构和JavaBean不一致怎么办?那就需要稍微改写一下查询,使结果集的结构和JavaBean保持一致。
例如,表的列名是office_address
,而JavaBean属性是workAddress
,就需要指定别名,改写查询如下:
1 | java复制代码SELECT id, email, office_address AS workAddress, name FROM users WHERE email = ? |
RowMapper 的基本使用
使用过原生JDBC的朋友应该知道,从数据库查询出来的记录全都被保存在ResultSet
结果集中,我们需要将结果集中的数据一条条地获取并设置到具体的实体类上,如此,该实体类才能在接下来的程序中使用。然而问题是,每次都要这么操作实在是太麻烦了,Spring就不应该提供什么功能来替我们做这些事情吗?答案当然是有的,那就是本文的主角——RowMapper
。
Spring JDBC
中目前有两个主要的RowMapper
实现,使用它们应该能解决大部分的场景了。
SingleColumnRowMapper
通过名字我们就能大概了解,在查询返回单列数据的时候,就该使用这个RowMapper,下面我们来看看具体的代码:
1 | java复制代码@Override |
测试:
1 | java复制代码@Test |
BeanPropertyRowMapper
当查询数据库返回的是多列数据,且你需要将这些多列数据映射到某个具体的实体类上,那么就该使用这个RowMapper,下面是具体的使用代码:
1 | java复制代码@Override |
这种使用方式有一个前提,那就是数据库SQL查出来的数据其列名与实体类中的属性名是一致的,当然个数和顺序可以不一致。比如数据库SQL查出来的姓名列叫name,那么对应的实体类中的姓名也必须叫name,而不能叫studentName或者其它。
定义自己的RowMapper
当然,如果你SQL查询出来的数据列名就是和实体类的属性名不一样,或者想按照自己的规则来装配实体类,那么就可以定义并使用自己的Row Mapper。
1 | java复制代码public class StudentRowMapper implements RowMapper<Student> { |
小结
- Spring提供了
JdbcTemplate
来简化JDBC操作; - 使用
JdbcTemplate
时,根据需要优先选择高级方法; - 任何JDBC操作都可以使用保底的
execute(ConnectionCallback)方法
。
本文转载自: 掘金