您可以捐助,支持我们的公益事业。

1元 10元 50元





认证码:  验证码,看不清楚?请点击刷新验证码 必填



  求知 文章 文库 Lib 视频 iPerson 课程 认证 咨询 工具 讲座 Model Center   Code  
会员   
   
 
     
   
 订阅
  捐助
JdbcTemplate完全学习
 
作者:下一站天亮了
   次浏览      
 2020-3-24
 
编辑推荐:
本文主要讲解JdbcTemplate类支持的回调类 ,jdbcTmeplate的CRUD操作 ,查询操作等希望对您能有所帮助。
本文来自于博客园,由火龙果软件Delores编辑推荐

概述

Spring JDBC抽象框架core包提供了JDBC模板类,其中JdbcTemplate是core包的核心类,所以其他模板类都是基于它封装完成的,JDBC模板类是第一种工作模式。

JdbcTemplate类通过模板设计模式帮助我们消除了冗长的代码,只做需要做的事情(即可变部分),并且帮我们做哪些固定部分,如连接的创建及关闭。

JdbcTemplate类对可变部分采用回调接口方式实现,如ConnectionCallback通过回调接口返回给用户一个连接,从而可以使用该连接做任何事情、StatementCallback通过回调接口返回给用户一个Statement,从而可以使用该Statement做任何事情等等,还有其他一些回调接口

Spring除了提供JdbcTemplate核心类,还提供了基于JdbcTemplate实现的NamedParameterJdbcTemplate类用于支持命名参数绑定、 SimpleJdbcTemplate类用于支持Java5+的可变参数及自动装箱拆箱等特性。

JdbcTemplate类支持的回调类:

预编译语句及存储过程创建回调:用于根据JdbcTemplate提供的连接创建相应的语句;

PreparedStatementCreator:通过回调获取JdbcTemplate提供的Connection,由用户使用该Conncetion创建相关的PreparedStatement;

CallableStatementCreator:通过回调获取JdbcTemplate提供的Connection,由用户使用该Conncetion创建相关的CallableStatement;

预编译语句设值回调:用于给预编译语句相应参数设值;

PreparedStatementSetter:通过回调获取JdbcTemplate提供的PreparedStatement,由用户来对相应的预编译语句相应参数设值;

BatchPreparedStatementSetter:;类似于PreparedStatementSetter,但用于批处理,需要指定批处理大小;

自定义功能回调:提供给用户一个扩展点,用户可以在指定类型的扩展点执行任何数量需要的操作;

ConnectionCallback:通过回调获取JdbcTemplate提供的Connection,用户可在该Connection执行任何数量的操作;

StatementCallback:通过回调获取JdbcTemplate提供的Statement,用户可以在该Statement执行任何数量的操作;

PreparedStatementCallback:通过回调获取JdbcTemplate提供的PreparedStatement,用户可以在该PreparedStatement执行任何数量的操作;

CallableStatementCallback:通过回调获取JdbcTemplate提供的CallableStatement,用户可以在该CallableStatement执行任何数量的操作;

结果集处理回调:通过回调处理ResultSet或将ResultSet转换为需要的形式;

RowMapper:用于将结果集每行数据转换为需要的类型,用户需实现方法mapRow(ResultSet rs, int rowNum)来完成将每行数据转换为相应的类型。

RowCallbackHandler:用于处理ResultSet的每一行结果,用户需实现方法processRow(ResultSet rs)来完成处理,在该回调方法中无需执行rs.next(),该操作由JdbcTemplate来执行,用户只需按行获取数据然后处理即可。

ResultSetExtractor:用于结果集数据提取,用户需实现方法extractData(ResultSet rs)来处理结果集,用户必须处理整个结果集;

下面详细讲解jdbcTmeplate的CRUD操作:

(一)增加、删除、修改操作:

1)增加、更新、删除(一条sql语句)(sql固定,不需要参数):

(a) int update(final String sql)

其中sql参数为需要传入的插入sql语句。

(b)int update(PreparedStatementCreator psc)

public void test() {
jdbcTemplate.update
(new PreparedStatementCreator() {
@Override
public PreparedStatement
createPreparedStatement(Connection conn)
throws SQLException {
return conn.prepareStatement
("insert into test(name) values('name1')");
}
});
}

(c)如果需要返回新插入数据的主键,采用如下方法(使用KeyHolder keyholder=new GeneratedKeyHolder();获得主键,jdbcTemplate和NamedParameterJdbcTemplate都可以通过此方法获得主键):

int update(PreparedStatementCreator psc, final KeyHolder generatedKeyHolder)

public void test() {
KeyHolder keyHolder = new
GeneratedKeyHolder();
jdbcTemplate.update
(new PreparedStatementCreator() {
@Override
public PreparedStatement
createPreparedStatement(Connection conn)
throws SQLException {
return conn.prepareStatement
("insert into test(name) values('name1')");
}
},keyHolder);
int i = keyHolder.getKey().intValue();
//这就是刚插入的数据的主键
}

2)增加、更新、删除(一条sql语句)(sql需要注入参数填充‘?’):

 (a)int update(String sql, PreparedStatementSetter pss)

public void test() {
String sql = "insert into test
(name) values (?)";
//返回的是更新的行数
int count = jdbcTemplate.update
(sql, new PreparedStatementSetter(){
@Override
public void setValues
(PreparedStatement pstmt)
throws SQLException {
pstmt.setObject(1, "name4");
}
});
}

(b)int update(String sql, Object[] args, int[] argTypes)

其中参数含义: sql:预处理sql语句; args:sql需要注入的参数; argTypes:需要注入的sql参数的JDBC类型(java.sql.Types中来获取类型的常量);

public void test() {
String sql = "insert into test
(name,age,create_date) values (?,?,?)";
Date now = new Date(System.
currentTimeMillis());
//返回的是更新的行数
int count = jdbcTemplate.update
(sql, new Object[]{"小明",14,now},
new int[]{Types.VARCHAR,Types.
INTEGER,Types.DATE});
}

(c)int update(String sql, Object... args)

其实内部还是调用方法a实现的,JdbcTemplate提供这种更简单的方式“update(String sql, Object... args)”来实现设值,所以只要当使用该种方式不满足需求时才应使用PreparedStatementSetter(上面方法a)。

public void test() {
String sql = "insert into test
(name,age,create_date) values (?,?,?)";
Date now = new Date
(System.currentTimeMillis());
//返回的是更新的行数
int count = jdbcTemplate.update
(sql, "小明", 14, now);
}

public void test() {
String sql = "insert into test
(name,age,create_date) values (?,?,?)";
Date now = new Date
(System.currentTimeMillis());
//返回的是更新的行数
int count = jdbcTemplate.update
(sql, new Object[]{"小明",14,now});
}

这两种实际上调用的都是该方法,由此可见Object...args实际上就是可变的数组,而数组长度是固定的,必须先定义一个数组,而Object...args在传递时参数可以任意,所以也可以传递一个固定的Object数组。

(d)int update(PreparedStatementCreator psc)

使用该方法可以自己使用原始jdbc方式给预编译sql注入参数,来进行增加、删除、更新操作:

public void test(final Customer customer)
{//参数也是局部变量,也必须用final修饰,
内部类中才能访问(全局变量不用)
//方法局部必须是final的,匿名内部类中才能引用
final String sql = "insert into test
(name,age,create_date) values (?,?,?)";
Date now = new Date
(System.currentTimeMillis());
//返回的是更新的行数
int count = jdbcTemplate.update
(new PreparedStatementCreator() {
@Override
public PreparedStatement
createPreparedStatement(Connection conn)
throws SQLException {
PreparedStatement ps
= conn.prepareStatement(sql);
ps.setString(1, customer.getName());
ps.setInt(2, customer.getAge());
ps.setDate(3, customer.getCreateDate());
return ps;
}
});
}

如果需要返回插入的主键,只能用此方法,增加KeyHolder参数:

public void test(final Customer customer)
{//参数也是局部变量,也必须用final修饰,
内部类中才能访问(全局变量不用)
KeyHolder keyHolder = new
GeneratedKeyHolder();
//方法局部必须是final的,
匿名内部类中才能引用
final String sql = "insert into test
(name,age,create_date) values (?,?,?)";
Date now = new Date
(System.currentTimeMillis());
//返回的是更新的行数
int count = jdbcTemplate.update
(new PreparedStatementCreator() {
@Override
public PreparedStatement
createPreparedStatement(Connection conn)
throws SQLException {
PreparedStatement ps = conn.prepareStatement
(sql,Statement.RETURN_GENERATED_KEYS);
//有的数据库版本不一样,
需要添加第二个参数,不然会报错;
ps.setString(1, customer.getName());
ps.setInt(2, customer.getAge());
ps.setDate(3, customer.getCreateDate());
return ps;
}
},keyHolder);
int i = keyHolder.getKey().intValue();
//这就是刚插入的数据的主键
}

3)批量增加、删除、更新数据(多条sql语句)

(a)批量执行多条sql(固定的sql,不需要注入参数,但是sql格式不固定)

int[] batchUpdate(final String[] sql)

参数是一个String数组,存放多条sql语句;返回值是int数组,即每条sql更新影响的行数。

 (b)批量执行多条sql(预处理sql,需要注入参数)

int[] batchUpdate(String sql, final BatchPreparedStatementSetter pss)

参数sql:一条预处理sql(如果是批量处理预处理sql,那么sql的格式就是固定的,只填充参数而已);第二个参数就是回调类,前面有统一介绍回调类。

举两个例子,一个更新,一个插入:

批量插入:
public void test(final
List<Customer> customer) {//参数也是局部变量,
也必须用final修饰,内部类中才能访问
(全局变量不用)
String sql = "insert into
test(name,age,create_date) values (?,?,?)";
//返回的是更新的行数
int[] count = jdbcTemplate.batchUpdate
(sql, new BatchPreparedStatementSetter(){
@Override
public void setValues
(PreparedStatement ps, int i)
throws SQLException {
//注入参数值
ps.setString(1, customer.get(i).getName());
ps.setInt(2, customer.get(i).getAge());
ps.setDate
(3, customer.get(i).getCreateDate());
}
@Override
public int getBatchSize() {
//批量执行的数量
return customer.size();
}
});
}

批量更新:
public void test(final
List<Customer> customer)
{//参数也是局部变量,也必须用final修饰,
内部类中才能访问(全局变量不用)
String sql = "update test
set name = ?,age = ? where id = ?";
//返回的是更新的行数
int[] count = jdbcTemplate.batchUpdate
(sql, new BatchPreparedStatementSetter(){
@Override
public void setValues
(PreparedStatement ps, int i)
throws SQLException {
//注入参数值
ps.setString(1, customer.get(i).getName());
ps.setInt(2, customer.get(i).getAge());
ps.setInt(3, customer.get(i).getId());
}
@Override
public int getBatchSize() {
//批量执行的数量
return customer.size();
}
});
}

 (c)批量处理多条预处理sql语句还有下面几种简单方法(参数和前面类似,这里就不详解):

int[] batchUpdate(String sql, List< Object[]> batchArgs);

int[] batchUpdate(String sql, List< Object[]> batchArgs, int[] argTypes);

前面讲了增加、删除、更新操作,这节讲一下查询。

查询操作:

(一)查询一个值(不需要注入参数)

queryForObject(String sql, Class< T > requiredType);

注意:参数requiredType只能是String,Integer这种类型,不能是自定义的实体类型,只能返回一个值,不能映射对象(映射对象下面会说);

 sql:预处理sql;requiredType:查询单列结果的类型;

public void test() {
String sql = "select count(*) from test";
int count = jdbcTemplate.queryForObject
(sql, Integer.class);
}

(二)查询一个值(使用预处理sql,需要注入参数)

queryForObject(String sql, Object[] args, Class< T > requiredType);

public void test(Integer id) {
String sql = "select name
from test where id = ?";
String name = jdbcTemplate.queryForObject
(sql, new Object[]{id}, String.class);
}

还有如下方式:queryForObject(String sql, Object[] args, int[] argTypes, Class< T > requiredType);

(三)查询单行记录,转换成一个对象(固定sql,不需要参数)

< T > T queryForObject(String sql, RowMapper< T > rowMapper)

public void test() {
String sql = "select name,age
from test where id = 10";
Customer customer =
jdbcTemplate.queryForObject
(sql, new RowMapper<Customer>() {
@Override
public Customer mapRow
(ResultSet rs, int i)
throws SQLException {
Customer c = new Customer();
c.setName(rs.getString("name"));
c.setAge(rs.getInt("age"));
return c;
}
});
}

(四)查询单行记录,转换成一个对象(预处理sql,需要注入参数)

< T > T queryForObject(String sql, Object[] args, RowMapper< T > rowMapper)

public void test(Integer id)
{//参数也是局部变量,也必须用final修饰,
内部类中才能访问(全局变量不用)
String sql = "select name,
age from test where id = ?";
Customer customer = jdbcTemplate.
queryForObject(sql, new Object[]{id},
new RowMapper<Customer>() {
@Override
public Customer mapRow
(ResultSet rs, int paramInt)
throws SQLException {
Customer c = new Customer();
c.setName(rs.getString("name"));
c.setAge(rs.getInt("age"));
return c;
}
});
}

也可以使用如下方式:(1)< T > T queryForObject(String sql, Object[] args, int[] argTypes, RowMapper< T > rowMapper);

(2)< T > T queryForObject(String sql, RowMapper< T > rowMapper, Object... args);

(五)查询数据库中一列多行数据,即查询数据库中单列数据存入一个list中,方式如下:(固定sql,没参数)

(a)List< Map< String, Object >> queryForList(String sql)

这个方法封装成map放入list中,key:列名(Oracle数据库sql执行结果列名默认为大写,需要小写用as取别名,别名用双引号) value:列的值

public void test() {//如果Oracle用这个sql查询,
返回的列名就是NAME(大写的),
对应Map里面的key就是NAME
String sql = "select name from
test where id > 0";
//如果用这个sql查询,返回的列名
就是name(小写的),对应Map里面的key就是name
String sql2 = "select name as
\"name\" from test where id > 0";
List<Map<String, Object>> list
= jdbcTemplate.queryForList(sql);
//这里用的是第一个sql
}

(b)< T > List< T > queryForList(String sql, Class< T > elementType)

这个方法就是直接将单类型数据存入List中。

注意:这个T虽然是泛型,但是只支持Integer.class String.class 这种单数据类型的,自己定义的Bean不支持。(所以用来查询单列数据)

public void test() {
String sql = "select name from
test where id > 0";
List<String> list = jdbcTemplate.
queryForList(sql, String.class);
}

(六)查询数据库中一列多行数据,即查询数据库中单列数据存入一个list中,方式如下:(预处理sql,需要注入参数)

(a)< T > List< T > queryForList(String sql, Object[] args, Class< T > elementType)

注意:这个T虽然是泛型,但是只支持Integer.class String.class 这种单数据类型的,自己定义的Bean不支持。(所以用来查询单列数据,同前面一个意思,后面不再解释)

public void test(Integer id) {
String sql = "select name from
test where id > ?";
List<String> list = jdbcTemplate.
queryForList(sql, new Object[]{id},
String.class);
}
 

还有如下方式实现:(1)< T > List< T > queryForList(String sql, Object[] args, int[] argTypes, Class< T > elementType);

(2)< T > List< T > queryForList(String sql, Class< T > elementType, Object... args);

(b)List< Map< String, Object >> queryForList(String sql, Object... args)

 封装成map存入List,和之前一样,要注意Oracle数据库返回的列名默认是大写的,如果需要,用别名变小写。

public void test(Integer id) {
String sql = "select name from
test where id > ?";
List<Map<String, Object>> list =
jdbcTemplate.queryForList
(sql, new Object[]{id});
}
 

还有一种方式实现:List< Map< String, Object >> queryForList(String sql, Object[] args, int[] argTypes);

(七)查询多条数据(固定sql,没有参数)

(a)< T > List< T > query(String sql, RowMapper< T > rowMapper)

 每条数据映射为java对象,放入List中。

public void test() {
String sql = "select name,
age from test where id > 10";
List<Customer> list = jdbcTemplate.
query(sql, new RowMapper<Customer>() {
@Override
public Customer mapRow
(ResultSet rs, int rowNum)
throws SQLException {
//这里必须new对象,不能在方法外new,
然后用同一个,因为是一个List,
查询出来多个对象映射,
//必须保证每一次调用都使用一个新的。
//如果不new,而是使用同一个对象,
会导致存入到List中的都是一样的对象
(都是最后一个对象)。
Customer customer = new Customer();
customer.setName(rs.getString("name"));
customer.setAge(rs.getInt("age"));
return customer;
}
});
}

该方法也可以把每一行数据转换为自定义key-value的map对象放入list中,如下:(所以说使用回调类比简单方法更强大,里面逻辑自己按需求写)

public void test() {
String sql = "select name,age
from test where id > 10";
List<Map<Integer,Object>>
list = jdbcTemplate.query
(sql, new RowMapper<Map<Integer,Object>>(){
@Override
public Map<Integer,Object>
mapRow(ResultSet rs, int rowNum)
throws SQLException {
Map<Integer, Object>
map = new HashMap<Integer, Object>();
map.put(rowNum, rs.getString("name"));
map.put(rowNum, rs.getInt("age"));
return map;
}
});
}

map中的key,value类型根据需要自定义,非常方便。像这种实现RowMapper< T >接口的匿名类,T可以为Map,也可以为自定义的对象类型,如上两种,根据需要选择。

(b) void query(String sql, RowCallbackHandler rch)

 注意:如果使用RowCallbackHandler 回调类,这个方法是没有返回值的,而是在回调类中将结果放入预先定义的List中,用法如下:

public void test() {
String sql = "select name,
age from test where id > 10";
//局部变量,必须用final修饰,
内部类中才能访问(全局变量不用)
final List<Customer> list
= new ArrayList<Customer>();
jdbcTemplate.query
(sql, new RowCallbackHandler() {
@Override
public void processRow(ResultSet rs)
throws SQLException {
Customer customer = new Customer();
customer.setName(rs.getString("name"));
customer.setAge(rs.getInt("age"));
list.add(customer);
}
});
}

当然,这种方式也可以转换为map,存入list中,和上面a方式一样,这里就不详解了。

(c)< T > T query(final String sql, final ResultSetExtractor< T > rse)

 ResultSetExtractor使用回调方法extractData(ResultSet rs)提供给用户整个结果集,让用户决定如何处理该结果集

public void test() {
String sql = "select name,age from
test where id > 10";
List<Customer>
list = jdbcTemplate.query
(sql, new ResultSetExtractor<List<Customer>>() {
@Override
public List<Customer> extractData(ResultSet rs)
throws SQLException, DataAccessException {
List<Customer> result = new ArrayList<Customer>();
while(rs.next()) {
Customer customer = new Customer();
customer.setName(rs.getString("name"));
customer.setAge(rs.getInt("age"));
result.add(customer);
}
return result;
}
});
}

同样也可以转换为map对象放入list中,如下:

public void test() {
String sql = "select name,age
from test where id > 10";
List<Map<String, Integer>>
list = jdbcTemplate.query
(sql, new ResultSetExtractor
<List<Map<String, Integer>>>() {
@Override
public List<Map<String, Integer>>
extractData(ResultSet rs)
throws SQLException, DataAccessException {
List<Map<String, Integer>>
result = new ArrayList<Map<String, Integer>>();
while(rs.next()) {
Map<String, Integer>
map = new HashMap<String, Integer>();
map.put(rs.getString("name"), rs.getInt("age"));
result.add(map);
}
return result;
}
});
}

(d)< T > List< T > query(PreparedStatementCreator psc, RowMapper< T > rowMapper)

public void test() {//局部变量,
必须用final修饰,内部类中才能访问
(全局变量不用)
final String sql = "select name,
age from test where id > 10";
List<Customer> list = jdbcTemplate.query
(new PreparedStatementCreator() {
@Override
public PreparedStatement
createPreparedStatement(Connection conn)
throws SQLException {
PreparedStatement ps = conn.prepareStatement(sql);
//如果sql是预处理的,需要传入参数,
可以在这里写jdbc代码传入,后面就不列举这种方法了
return ps;
}
}, new RowMapper<Customer>() {
@Override
public Customer mapRow(ResultSet rs, int rowNum)
throws SQLException {
Customer customer = new Customer();
customer.setAge(rs.getInt("age"));
customer.setName(rs.getString("name"));
return customer;
}
});
}

可以将RowMapper换成ResultSetExtractor或者RowCallbackHandler回调类,和前面一样,因此还有下面两种方法:

(1)void query(PreparedStatementCreator psc, RowCallbackHandler rch);

(2)< T > T query(PreparedStatementCreator psc, ResultSetExtractor< T > rse);

(八)查询多条数据(预处理sql,需要传入参数)

(a)< T > List< T > query(String sql, PreparedStatementSetter pss, RowMapper< T > rowMapper)

public void test(final Integer id)
{//参数也是局部变量,也必须用final修饰,
内部类中才能访问(全局变量不用)
String sql = "select name,
age from test where id > ?";
List<Customer> list = jdbcTemplate.
query(sql, new PreparedStatementSetter() {
@Override
public void setValues(PreparedStatement ps)
throws SQLException {
ps.setInt(1, id);
}
}, new RowMapper<Customer>() {
@Override
public Customer mapRow
(ResultSet rs, int rowNum)
throws SQLException {
Customer customer = new Customer();
customer.setName(rs.getString("name"));
customer.setAge(rs.getInt("age"));
return customer;
}
});
}

用RowMapper回调类还有三种方法:

 (1)< T > List< T > query(String sql, Object[] args, int[] argTypes, RowMapper< T > rowMapper);

 (2)< T > List< T > query(String sql, Object[] args, RowMapper< T > rowMapper);

 (3)< T> List< T > query(String sql, RowMapper< T > rowMapper, Object... args);

而如果把回调类换成ResultSetExtractor或者RowCallbackHandler回调类,又有八种方法(像上面一样各有四种),这里就不举出来了,和前面用法一致。

使用Spring的JdbcTemplate或者NamedParameterJdbcTemplate查询数据库,获取结果,数据库表字段和实体类自动对应,可以使用BeanPropertyRowMapper。

注意:

 自动绑定,需要列名称和Java实体类名字一致,如:属性名 “userName” 可以匹配数据库中的列字段(这里说的列字段是sql执行结果的列名,也就是如果有别名就用别名,(Oracle默认列名大写)) "USERNAME" 或 “user_name”。这样,我们就不需要一个个手动绑定了,大大提高了开发效率。

下面讲解BeanPropertyRowMapper用法:

BeanPropertyRowMapper< T > implements RowMapper< T >这个类是实现了RowMapper接口的,所以之前讲的查询中,能用到RowMapper回调类的都可以用BeanPropertyRowMapper来将结果直接映射为实体类。

public List<UserEntity>
findUser(UserEntity user) {
List<UserEntity> userList = jdbcTemplate.query
(SEL_BY_USERNAME_PWD,
new Object[] { user.getUserName(),
user.getPwd() },
new BeanPropertyRowMapper<UserEntity>
(UserEntity.class));
return userList;
}

正如上面,直接利用BeanPropertyRowMapper的构造方法传递一个需要映射的类的class对象进去即可实现,当然必须满足之前说的要求:

1.属性名“userName”要按如下规则匹配sql结果列:结果列要是"user_name"(大小写都行),因为BeanPropertyRowMapper的中会将结果列都转为小写去和对象中set属性对应;

2.属性名“user”这对应结果列为“USER”(大小写都行),理由同上;

 
   
次浏览       
相关文章

Java微服务新生代之Nacos
深入理解Java中的容器
Java容器详解
Java代码质量检查工具及使用案例
相关文档

Java性能优化
Spring框架
SSM框架简单简绍
从零开始学java编程经典
相关课程

高性能Java编程与系统性能优化
JavaEE架构、 设计模式及性能调优
Java编程基础到应用开发
JAVA虚拟机原理剖析