Spring中的JdbaTemplate
JdbcTemplate概述
Spring对数据库的操作在jdbc上面做了基本的封装,让开发者在操作数据库时只需关注SQL语句和查询结果处理器,即可完成功能(当然,只使用JdbcTemplate,还不能摆脱持久层实现类的编写)。
在配合spring的IoC功能,可以把DataSource注册到JdbcTemplate之中。同时利用spring基于aop的事务即可完成简单的数据库CRUD操作。
JdbcTemplate的限定命名为org.springframework.jdbc.core.JdbcTemplate。要使用JdbcTemlate需要导入spring-jdbc和spring-tx两个坐标。
源码
/**
* JdbcTemplate实现了JdbcOperations接口,操作方法都定义在此接口中
*/
public class JdbcTemplate extends JdbcAccessor implements JdbcOperations {
/**
* 使用默认构造函数构建JdbcTemplate
*/
public JdbcTemplate() {
}
/**
* 通过数据源构建JdbcTemplate
*/
public JdbcTemplate(DataSource dataSource) {
setDataSource(dataSource);
afterPropertiesSet();
}
/**
* 当使用默认构造函数构建时,提供了设置数据源的方法
*/
public void setDataSource(@Nullable DataSource dataSource) {
this.dataSource = dataSource;
}
}
使用 JdbcTemplate
JdbcTemplate
是 JDBC 核心软件包中的中心类。它处理资源的创建和释放,这有助于您避免常见的错误,例如忘记关闭连接。它执行核心 JDBC 工作流程的基本任务(例如,语句创建和执行),而使应用程序代码提供 SQL 并提取结果。 JdbcTemplate
类:
- 运行 SQL 查询
- 更新语句和存储过程调用
- 对
ResultSet
个实例执行迭代并提取返回的参数值。 - 捕获 JDBC 异常,并将其转换为
org.springframework.dao
包中定义的通用,信息量更大的异常层次结构。 (请参阅一致的异常层次结构。)
在代码中使用JdbcTemplate
时,只需实现回调接口,即可为它们明确定义 Contract。给定JdbcTemplate
类提供的Connection
,PreparedStatementCreator
回调接口将创建一条准备好的语句,提供 SQL 和任何必要的参数。 CallableStatementCreator
接口(创建可调用语句)也是如此。 RowCallbackHandler
接口从ResultSet
的每一行提取值。
您可以通过直接实例化DataSource
引用在 DAO 实现中使用JdbcTemplate
,也可以在 Spring IoC 容器中对其进行配置,并将其作为 Bean 引用提供给 DAO。
Note
DataSource
应该始终配置为 Spring IoC 容器中的 bean。在第一种情况下,将 Bean 直接提供给服务。在第二种情况下,将其提供给准备好的模板。
此类发出的所有 SQL 都以DEBUG
级别记录在与模板实例的标准类名相对应的类别下(通常为JdbcTemplate
,但是如果使用JdbcTemplate
类的自定义子类,则可能有所不同)。
以下各节提供了JdbcTemplate
用法的一些示例。这些示例不是JdbcTemplate
公开的所有功能的详尽列表。见服务员javadoc。
Querying (SELECT)
以下查询获取关系中的行数:
int rowCount = this.jdbcTemplate.queryForObject("select count(*) from t_actor", Integer.class);
以下查询使用绑定变量:
int countOfActorsNamedJoe = this.jdbcTemplate.queryForObject(
"select count(*) from t_actor where first_name = ?", Integer.class, "Joe");
以下查询查找String
:
String lastName = this.jdbcTemplate.queryForObject(
"select last_name from t_actor where id = ?",
new Object[]{1212L}, String.class);
以下查询查找并填充单个域对象:
Actor actor = this.jdbcTemplate.queryForObject(
"select first_name, last_name from t_actor where id = ?",
new Object[]{1212L},
new RowMapper<Actor>() {
public Actor mapRow(ResultSet rs, int rowNum) throws SQLException {
Actor actor = new Actor();
actor.setFirstName(rs.getString("first_name"));
actor.setLastName(rs.getString("last_name"));
return actor;
}
});
以下查询查找并填充许多域对象:
List<Actor> actors = this.jdbcTemplate.query(
"select first_name, last_name from t_actor",
new RowMapper<Actor>() {
public Actor mapRow(ResultSet rs, int rowNum) throws SQLException {
Actor actor = new Actor();
actor.setFirstName(rs.getString("first_name"));
actor.setLastName(rs.getString("last_name"));
return actor;
}
});
如果最后两个代码段确实存在于同一应用程序中,则删除两个RowMapper
匿名内部类中存在的重复并将它们提取到单个类(通常是static
嵌套类)中,然后可以引用该重复是有意义的。根据需要使用 DAO 方法。例如,最好编写以下代码片段,如下所示:
public List<Actor> findAllActors() {
return this.jdbcTemplate.query( "select first_name, last_name from t_actor", new ActorMapper());
}
private static final class ActorMapper implements RowMapper<Actor> {
public Actor mapRow(ResultSet rs, int rowNum) throws SQLException {
Actor actor = new Actor();
actor.setFirstName(rs.getString("first_name"));
actor.setLastName(rs.getString("last_name"));
return actor;
}
}
使用 JdbcTemplate 更新(INSERT,UPDATE 和 DELETE)
您可以使用update(..)
方法执行插入,更新和删除操作。参数值通常作为变量参数提供,或者作为对象数组提供。
下面的示例插入一个新条目:
this.jdbcTemplate.update(
"insert into t_actor (first_name, last_name) values (?, ?)",
"Leonor", "Watling");
以下示例更新现有条目:
this.jdbcTemplate.update(
"update t_actor set last_name = ? where id = ?",
"Banjo", 5276L);
下面的示例删除一个条目:
this.jdbcTemplate.update(
"delete from actor where id = ?",
Long.valueOf(actorId));
其他 JdbcTemplate 操作
您可以使用execute(..)
方法来运行任意 SQL。因此,该方法通常用于 DDL 语句。带有回调接口,绑定变量数组等的变体极大地超载了它。以下示例创建一个表:
this.jdbcTemplate.execute("create table mytable (id integer, name varchar(100))");
下面的示例调用一个存储过程:
this.jdbcTemplate.update(
"call SUPPORT.REFRESH_ACTORS_SUMMARY(?)",
Long.valueOf(unionId));
JdbcTemplate 最佳做法
JdbcTemplate
类的实例一旦配置便是线程安全的。这很重要,因为这意味着您可以配置JdbcTemplate
的单个实例,然后将该共享引用安全地注入到多个 DAO(或存储库)中。 JdbcTemplate
是有状态的,因为它维护对DataSource
的引用,但是此状态不是会话状态。
使用JdbcTemplate
类(和关联的NamedParameterJdbcTemplate类)的常见做法是在 Spring 配置文件中配置DataSource
,然后将共享的DataSource
bean 依赖注入到 DAO 类中。在DataSource
的设置器中创建JdbcTemplate
。这将导致类似于以下内容的 DAO:
public class JdbcCorporateEventDao implements CorporateEventDao {
private JdbcTemplate jdbcTemplate;
public void setDataSource(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
// JDBC-backed implementations of the methods on the CorporateEventDao follow...
}
以下示例显示了相应的 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
http://www.springframework.org/schema/context/spring-context.xsd">
<bean id="corporateEventDao" class="com.example.JdbcCorporateEventDao">
<property name="dataSource" ref="dataSource"/>
</bean>
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="${jdbc.driverClassName}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<context:property-placeholder location="jdbc.properties"/>
</beans>
显式配置的替代方法是使用组件扫描和 Comments 支持进行依赖项注入。在这种情况下,可以用@Repository
Comments 该类(这使其成为组件扫描的候选对象),并用@Autowired
CommentsDataSource
setter 方法。以下示例显示了如何执行此操作:
@Repository (1)
public class JdbcCorporateEventDao implements CorporateEventDao {
private JdbcTemplate jdbcTemplate;
@Autowired (2)
public void setDataSource(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource); (3)
}
// JDBC-backed implementations of the methods on the CorporateEventDao follow...
}
- (1) 用
@Repository
Comments 类。 - (2) 用
@Autowired
CommentsDataSource
setter 方法。 - (3) 用
DataSource
创建一个新的JdbcTemplate
。
以下示例显示了相应的 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
http://www.springframework.org/schema/context/spring-context.xsd">
<!-- Scans within the base package of the application for @Component classes to configure as beans -->
<context:component-scan base-package="org.springframework.docs.test" />
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="${jdbc.driverClassName}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<context:property-placeholder location="jdbc.properties"/>
</beans>
如果使用 Spring 的JdbcDaoSupport
类,并且各种 JDBC 支持的 DAO 类都从该类扩展,则您的子类将从JdbcDaoSupport
类继承setDataSource(..)
方法。您可以选择是否从此类继承。提供JdbcDaoSupport
类只是为了方便。
无论您选择使用(或不使用)以上哪种模板初始化样式,都无需在每次运行 SQL 时都创建一个新的JdbcTemplate
类实例。配置完成后,JdbcTemplate
实例是线程安全的。如果您的应用程序访问多个数据库,则可能需要多个JdbcTemplate
实例,这需要多个DataSources
实例,然后需要多个不同配置的JdbcTemplate
实例。
示例代码
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;
/**
* 创建数据源并存入Ioc容器
* @return
*/
@Bean
public DataSource createDataSource(){
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName(driver);
dataSource.setUrl(url);
dataSource.setUsername(username);
dataSource.setPassword(password);
return dataSource;
}
/**
* 创建JdbcTemplate对象
* @param dataSource
* @return
*/
@Bean
public JdbcTemplate createJdbcTemplate(DataSource dataSource){
return new JdbcTemplate(dataSource);
}
@Bean
public LobHandler createLobHandler(){
return new DefaultLobHandler();
}
@Bean
public NamedParameterJdbcTemplate createNamedParameterJdbcTemplate(JdbcTemplate jdbcTemplate){
return new NamedParameterJdbcTemplate(jdbcTemplate);
}
}
@Configuration
@Import(JdbcConfig.class)
@PropertySource("classpath:jdbc.properties")
public class SpringConfiguration {
}
测试代码:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfiguration.class)
public class SpringJdbcTemplateTest {
@Autowired
private JdbcTemplate jdbcTemplate;
@Test
public void testSave(){
jdbcTemplate.update("insert into account(money,name)values(?,?)",6789d,"userTest");
}
@Test
public void testUpdate(){
jdbcTemplate.update("update account set name=?,money=? where id=?","testZZZ",23456d,3);
}
@Test
public void testDelete(){
jdbcTemplate.update("delete from account where id = ? ",4);
}
@Test
public void testFindOne(){
// List<Account> accounts = jdbcTemplate.query("select * from account where id = ?",new BeanPropertyRowMapper<Account>(Account.class),1);
// System.out.println(accounts.isEmpty()?"empty":accounts.get(0));
// Account account = jdbcTemplate.queryForObject("select * from account where id = ?",new BeanPropertyRowMapper<Account>(Account.class),1);
// System.out.println(account);
Account account = jdbcTemplate.query("select * from account where id = ?", (ResultSet rs) ->{
Account account1 = null;
//1.判断结果集能往下走
if(rs.next()){
account1 = new Account();
account1.setId(rs.getInt("id"));
account1.setName(rs.getString("name"));
account1.setMoney(rs.getDouble("money"));
}
return account1;
}, 1);
System.out.println(account);
}
@Test
public void testFindAll(){
List<Account> accountList = jdbcTemplate.query("select * from account where money > ?",new BeanPropertyRowMapper<Account>(Account.class),999d);
for(Account account : accountList){
System.out.println(account);
}
}
@Test
public void testFindCount(){
Integer count = jdbcTemplate.queryForObject("select count(*) from account where money > ?",Integer.class,999d);
System.out.println(count);
}
@Test
public void testQueryForList(){
/**
* 得到某个特定类型的集合。类型是方法的第二个参数指定的
*/
List<Double> list = jdbcTemplate.queryForList("select money from account where money > ?",Double.class,999d);
for(Double money : list){
System.out.println(money);
}
List<Map<String,Object>> list2 = jdbcTemplate.queryForList("select * from account where money > ? ",999d);
list2.forEach(m ->{
m.forEach( (k,v) ->{
System.out.println(k+","+v);
});
});
}
@Test
public void testQueryForMap(){
Map<String,Object> map = jdbcTemplate.queryForMap("select * from account where id = ?",1);
for(Map.Entry<String,Object> me : map.entrySet()) {
System.out.println(me.getKey()+","+me.getValue());
}
}
@Test
public void testQueryForRowSet(){
SqlRowSet rowSet = jdbcTemplate.queryForRowSet("select * from account where money > ?",999d);
System.out.println(rowSet);
while(rowSet.next()){
String name = rowSet.getString("name");
System.out.println(name);
}
}
}