前面我们学习了有关JDBC的封装,如何让JDBC使用更加灵活。用到的技术手段简单应用有很多。现在小结如下:
那么,上面的这些对JDBC的封装优化等,spring-Jdbc已经做好了。更加的强大!
Spring对数据库的操作在jdbc上面做了深层次的封装,使用spring的注入功能,可以把DataSource注册到JdbcTemplate之中。JdbcTemplate其全限定命名:org.springframework.jdbc.core.JdbcTemplate。要使用JdbcTemlate还需一个这个包,其包含了一下事务和异常控制
Spring JDBC – 框架和应用开发者各自分工
也就是说Spring-JDBC帮你屏蔽了很多JDBC底层繁琐的API操作、让你更方便的开发
org.springframework.jdbc.core
org.springframework.jdbc.datasource
org.springframework.jdbc.object
此包针对JDBC做了很多上层封装、而底层依赖于org.springframework.jdbc.core包。
org.springframework.jdbc.support
JdbcTemplate
JdbcTemplate是JDBC core包里面的核心类
。它封装了对资源的创建和释放,可以帮你避免忘记关闭连接等常见错误。它也包含了核心JDBC工作流的一些基础工作、例如执行和声明语句,而把SQL语句的生成以及查询结果的提取工作留给应用代码。JdbcTemplate执行查询、更新SQL语句和调用存储过程,运行结果集迭代和抽取返回参数值。它也可以捕获JDBC异常并把它们转换成更加通用、解释性更强的异常层次结构、这些异常都定义在org.springframework.dao包
里面。
当你在代码中使用了JdbcTemplate类,你只需要实现回调接口。PreparedStatementCreator回调接口通过传入的Connection类(该类包含SQL和任何必要的参数)创建已声明的语句。CallableStatementCreator也提供类似的方式、该接口用于创建回调语句。RowCallbackHandler用于获取结果集每一行的值。
可以在DAO实现类中通过传入DataSource引用来完成JdbcTemplate的初始化;也可以在Spring IOC容器里面配置、作为DAO bean的依赖Bean配置。
DataSource最好在Spring IOC容器里作为Bean配置起来。在上面第一种情况下,DataSource bean直接传给相关的服务;第二种情况下DataSource bean传递给JdbcTemplate bean。
JdbcTemplate中使用的所有SQL以“DEBUG”级别记入日志(一般情况下日志的归类是JdbcTemplate对应的全限定类名,不过如果需要对JdbcTemplate进行定制的话,可能是它的子类名)
JdbcTemplate主要提供以下五类方法:
JdbcTemplate实例一旦配置之后是线程安全的。这点很重要因为这样你就能够配置JdbcTemplate的单例,然后安全的将其注入到多个DAO中(或者repositories)。JdbcTemplate是有状态的,内部存在对DataSource的引用,但是这种状态不是会话状态。
使用JdbcTemplate类的常用做法是在你的Spring配置文件里配置好一个DataSource,然后将其依赖注入进你的DAO类中
JdbcTemplate - 这是经典的也是最常用的Spring对于JDBC访问的方案。这也是最低级别的封装
, 其他的工作模式事实上在底层使用了JdbcTemplate作为其底层的实现基础。JdbcTemplate在JDK 1.4以上的环境上工作得很好。
NamedParameterJdbcTemplate - 对JdbcTemplate做了封装,提供了更加便捷的基于命名参数的使用方式而不是传统的JDBC所使用的“?”作为参数的占位符
。这种方式在你需要为某个SQL指定许多个参数时,显得更加直观而易用。该特性必须工作在JDK 1.4以上。
SimpleJdbcTemplate - 这个类结合了JdbcTemplate和NamedParameterJdbcTemplate的最常用的功能,同时它也利用了一些Java 5的特性所带来的优势
,例如泛型、varargs和autoboxing等,从而提供了更加简便的API访问方式。需要工作在Java 5以上的环境中。
SimpleJdbcInsert 和 SimpleJdbcCall - 这两个类可以充分利用数据库元数据的特性来简化配置。通过使用这两个类进行编程,你可以仅仅提供数据库表名或者存储过程的名称以及一个Map作为参数。其中Map的key需要与数据库表中的字段保持一致。这两个类通常和SimpleJdbcTemplate配合使用。这两个类需要工作在JDK 5以上,同时数据库需要提供足够的元数据信息。
RDBMS 对象包括MappingSqlQuery, SqlUpdate and StoredProcedure - 这种方式允许你在初始化你的数据访问层时创建可重用并且线程安全的对象。该对象在你定义了你的查询语句,声明查询参数并编译相应的Query之后被模型化。
一旦模型化完成,任何执行函数就可以传入不同的参数对之进行多次调用。这种方式需要工作在JDK 1.4以上。
1、数据源
datasource = BasicDataSourceFactory.createDataSource(properties);
2、实现CRUD
/**
* 使用JdbcTemplate实现对数据的增删改查。
* 1、什么是JdbcTemplate?
* + JdbcTemplate是Spring提供的访问数据库的方式之一,是Spring中最基本、最底层的访问数据库的实现方式。
* + 通过使用JdbcTemplate,开发者无需关心数据库连接的创建和关闭细节,只需要专注于实现业务逻辑即可。
* + 在使用JdbcTemplate的时候,只需要声明即可,无需自己初始化,因为Spring在初始化数据源datasource的时候会
* 自己创建JdbcTemplate的实例。
*/
public class JdbcTemplateTest implements Dao {
//使用数据源连接池来注册JDBC
private JdbcTemplate jdbc = new JdbcTemplate(JDBCUtils.getDatasource());
public static void main(String[] args) {
JdbcTemplateTest jdbcTest = new JdbcTemplateTest();
User user = new User();
user.setName("李一平");
user.setMoney(500f);
jdbcTest.addUser(user);
System.out.println("插入user: " + user);
}
@Override
public void addUser(User user) {
final String sql = "insert into bank(name,money) values(?,?)";
final Object[] args = new Object[]{user.getName(), user.getMoney()};
//获取刚插入的主键并返回
KeyHolder holder = new GeneratedKeyHolder();
jdbc.update(new PreparedStatementCreator() {
@Override
public PreparedStatement createPreparedStatement(Connection connection) throws SQLException {
PreparedStatement ps = connection.prepareStatement(sql, new String[]{"id"});
for (int i = 0; i < args.length; i++) {
ps.setObject(i + 1, args[i]);
}
return ps;
}
}, holder);
int id = Objects.requireNonNull(holder.getKey()).intValue();
user.setId(id);
}
@Override
public void deleteUser(String userName) {//删除操作
String sql = "delete from bank where name=?";
Object[] args = new Object[]{userName};
jdbc.update(sql, args);
}
@Override
public void updateUser(User user) {//更新操作
String sql = "update bank set name=? set money=? where id=?";
Object[] args = new Object[]{user.getName(), user.getMoney(), user.getId()};
jdbc.update(sql, args);//返回影响的记录条数
}
@Override
public User findUser(String userName) {//查询
String sql = "select * from bank where name=?";
Object[] args = new Object[]{userName};
//我们原本的实现是创建一个结果集处理接口,实现处理方法。JdbcTemplate类似,只不过是利用了结果集参数反射获取属性值。
User user = (User) jdbc.queryForObject(sql, args, new BeanPropertyRowMapper<User>(User.class));
if (user != null)
return user;
else throw new NullPointerException("空异常!");
}
public String findUserById(int id) {
String sql = "select name from bank where id=?";
Object[] args = new Object[]{id};
return jdbc.queryForObject(sql, args, String.class);
}
@Override
public boolean toUser(User user, User toUser, float money) {
if (money > 0.0001) {
user.setMoney(user.getMoney() - money);
toUser.setMoney(toUser.getMoney() + money);
updateUser(user);
updateUser(toUser);
return true;
} else
throw new RuntimeException("转账金额不能为负数!");
}
}
我们在IDEA里面点击该类,然后点击View-》ToolWindows-》Structure
queryForObject
spring 3.2.2 版本之后jdbcTemplate.queryForInt()和jdbcTemplate.queryForLong() 就取消了,全部用queryForObject代替了
queryForObject(sql, requiredType)
本质上和queryForInt相同,只是可以返回不同的对象,例如返回一个String对象
2个参数——sql语句 、期待返回来的对象类型
queryForObject(sql, requiredType,args…)
第三个参数是个可变参数
queryForObject(sql, args[],requiredType)
第二个参数是个参数数组
queryForObject(sql, rowMapper)
注意,这里查询必须保证只能查询一条数据,否则会报错。
queryForList
返回一个装有map的list,每一个map是一条记录
,map里面的key是字段名
List
queryForMap
这个查询只能是查询一条记录的查询,返回一个map,key的值是column的值
queryForRowSet
返回一个结果集然后调用.getString或者getInt等去取值
query()方法
sping中的RowMapper可以将数据中的每一行数据封装成用户定义的类。
我们在数据库查询中,如果返回的类型是用户自定义的类型(其实我们在数据库查询中大部分返回的都是自定义的类)则需要包装,如果是Java自定义的类型,如:String则不需要。
如果sping与hibernate 相结合了,基本上是用不到,大多数都是在spring单独使用时用到,常见的情况就是与JdbcTemplate一起使用。
可以通过建立内部类实现RowMapper接口,RowMapper中有一个mapRow方法,所以实现RowMapper接口一定要实现mapRow方法,而对自定义类的包装就在mapRow方法中实现。
BeanPropertyRowMapper是RowMapper将表行转换为指定映射目标类的新实例的实现
。映射的目标类必须是顶级类,并且必须具有默认或无参数构造函数
。
使用BeanPropertyRowMapper自动绑定,需要确保数据库表列名称与Java实体类属性名称相同
,否则使用别名,则对应的属性值返回的是null或者默认值,而不是报错。
如:
@Override
public User findUser(String userName) {//查询
String sql = "select id as i,name as n,money as m from bank where name=?";
Object[] args = new Object[]{userName};
//我们原本的实现是创建一个结果集处理接口,实现处理方法。JdbcTemplate类似,只不过是利用了结果集参数反射获取属性值。
User user = (User) jdbc.queryForObject(sql, args, new BeanPropertyRowMapper<User>(User.class));
if (user != null)
return user;
else throw new NullPointerException("空异常!");
}
测试代码:
JdbcTemplateTest jdbcTest = new JdbcTemplateTest();
User user = new User();
jdbcTest.findUser("王二小");
System.out.println(user);
String sql = "select id,name,money as m from bank where name=?";
KeyHolder用于获取约束键,如主键。是一个接口。
public interface KeyHolder {
Number getKey() throws InvalidDataAccessApiUsageException;
Map<String, Object> getKeys() throws InvalidDataAccessApiUsageException;
List<Map<String, Object>> getKeyList();
}
而spring有对KeyHolder的默认实现GeneratedKeyHolder。
Spring利用GeneratedKeyHolder,提供了一个可以返回新增记录对应主键值的方法:
int update(PreparedStatementCreator psc, KeyHolder generatedKeyHolder)
通用的实现类GeneratedKeyHolder,该类返回新增记录时的自增长主键值。对应的方法是getKey(),注意返回的类型是number。使用时需要转化。
如获取插入User的主键id:
@Override
public void addUser(User user) {
final String sql = "insert into bank(name,money) values(?,?)";
final Object[] args = new Object[]{user.getName(), user.getMoney()};
//获取刚插入的主键并返回
KeyHolder holder = new GeneratedKeyHolder();
jdbc.update(new PreparedStatementCreator() {
@Override
public PreparedStatement createPreparedStatement(Connection connection) throws SQLException {
PreparedStatement ps = connection.prepareStatement(sql, new String[]{"id"});
for (int i = 0; i < args.length; i++) {
ps.setObject(i + 1, args[i]);
}
return ps;
}
}, holder);
int id = Objects.requireNonNull(holder.getKey()).intValue();
user.setId(id);
}
解析:
keyHolder 是数据库自增主键值的持有者,它监听PreparedStatement的返回的值
Statement.RETURN_GENERATED_KEYS获取主键值,并存放在自己的池中(实际上是一个list);也可以传入具体类型参数。但是声明获取主键值更加规范。
一般来说,一个keyHolder实例只绑定一个PreparedStatement的执行,当然最好也只是插入一条数据库记录,这样才能保证池中只有一个主键值。
当keyHolder获得主键值后,您可以在任何时候通过访问keyHolder对象得到这个主键值,也就是说只要它的生命期存在,这个主键的值就一直不会丢失。
总结: