为了使 JDBC 更加易于使用,Spring 在JDBC的API 上定义了一个抽象层, 以此建立了一个JDBC存取框架。
作为 Spring JDBC 框架的核心, JDBC 模板的设计目的是为不同类型的JDBC操作提供模板方法。每个模板方法都能控制整个过程,并允许覆盖过程中的特定任务。通过这种方式,可以在尽可能保留灵活性的情况下,将数据库存取的工作量降到最低。
JdbcTemplate主要提供以下五类方法:
- execute方法:可以用于执行任何SQL语句,一般用于执行DDL语句
- update方法及batchUpdate方法:update方法用于执行新增、修改、删除等语句
- batchUpdate方法用于执行批处理相关语句
- query方法及queryForXXX方法:用于执行查询相关语句
- call方法:用于执行存储过程、函数相关语句
使用示例:
在数据库中先准备两张表:
在java工程中创建两个对应的实体类:
public class Department {
int id;
String deptName;
@Override
public String toString() {
return "Department [id=" + id + ", deptName=" + deptName + "]";
}
}
public class Employee {
int id;
String lastName;
String email;
Department department;
@Override
public String toString() {
return "Employee [id=" + id + ", lastName=" + lastName + ", email="
+ email + ", department=" + department + "]";
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public Department getDepartment() {
return department;
}
public void setDepartment(Department department) {
this.department = department;
}
}
引入Spring框架相关的jar包以及c3p0和mysql连接jar包。为了对方法进行测试,这里还需要引入JUnit4.这里以导入外部属性文件的方式来配置数据源:
jdbc.properties文件内容如下:
user=root
password=123
driverClass=com.mysql.jdbc.Driver
jdbcUrl=jdbc:mysql:///spring
initPoolSize=5
maxPoolSize=10
在ApplicationContext.xml文件中,导入这个属性文件以及配置c3p0数据源:
配置好dataSource后就可以用这个数据源来配置JdbcTemplate了,在xml文件中添加:
接下来创建一个测试类对JdbcTemplate的方法进行测试:
import java.util.ArrayList;
import java.util.List;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
public class JDBCTest {
private ApplicationContext ctx= null;
private JdbcTemplate jdbcTemplate = null;
// private EmployeeDao employee;
{
ctx = new ClassPathXmlApplicationContext("ApplicationContext.xml");
jdbcTemplate = (JdbcTemplate) ctx.getBean("jdbcTemplate");
}
/**
* 执行 INSERT,UPDATE,DELETE
*/
@Test
public void testUpdate() {
String sql = "UPDATE employees SET last_name = ? WHERE id = ?";
jdbcTemplate.update(sql, "Jack", 5);
}
/**
* 测试批量更新操作
* 最后一个参数是 Object[] 的 List 类型:因为修改一条记录需要一个 Object 数组,修改多条记录就需要一个 List 来存放多个数组。
*/
@Test
public void testBatchUpdate() {
String sql = "INSERT INTO employees(last_name, email, dept_id) VALUES(?,?,?)";
List
在实际的使用中,一般会创建一个dao类来封装对某个对象的所有增删改查操作.
比如,创建一个EmployeeDao类如下:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Repository;
@Repository
public class EmployeeDao {
@Autowired
private JdbcTemplate jdbcTemplate;
public Employee get(Integer id) {
String sql = "SELECT id, last_name lastName, email FROM employees WHERE id = ?";
RowMapper rowMapper = new BeanPropertyRowMapper<>(Employee.class);
Employee employee = jdbcTemplate.queryForObject(sql, rowMapper, id);
return employee;
}
}
在这个Dao类中实现了通过id来获取记录并封装成对象返回的方法。如果有需要还可以实现其他操作如插入、删除、更新等。
由于这里使用了注解来配置bean以及bean的自动装配,所以还需要在xml文件中添加(要先导入context命名空间):
测试一下EmployeeDao:
@Test
public void testEmployeeDao() {
EmployeeDao employeeDao = (EmployeeDao) ctx.getBean("employeeDao");
Employee employee = employeeDao.get(1);
System.out.println(employee);
}
打印输出如下:
现在列出JdbcTemplate的实现案例
创建表:
/**
* 创建表
*/
public void create(String tableName){
//tableName传入tb_test1
String sql = "create table "+tableName +" (id integer,user_name varchar2(40),password varchar2(40)) ";
jdbcTemplate.execute(sql);
}
插入表数据:
//jdbcTemplate.update适合于insert 、update和delete操作;
/**
* 第一个参数为执行sql
* 第二个参数为参数数据
*/
public void save(User user) {
Assert.isNull(user, "user is not null");
jdbcTemplate.update("insert into tb_test1(name,password) values(?,?)",
new Object[]{user.getUsername(),user.getPassword()});
}
插入数据:
/**
* 第一个参数为执行sql
* 第二个参数为参数数据
* 第三个参数为参数类型
*/
@Override
public void save(User user) {
Assert.isNull(user, "user is not null");
jdbcTemplate.update(
"insert into tb_test1(name,password) values(?,?)",
new Object[]{user.getUsername(),user.getPassword()},
new int[]{java.sql.Types.VARCHAR,java.sql.Types.VARCHAR}
);
}
插入数据::
//避免sql注入
public void save(final User user) {
Assert.isNull(user, "user is not null");
jdbcTemplate.update("insert into tb_test1(name,password) values(?,?)",
new PreparedStatementSetter(){
@Override
public void setValues(PreparedStatement ps) throws SQLException {
ps.setString(1, user.getUsername());
ps.setString(2, user.getPassword());
}
});
}
更新数据:
public void update(final User user) {
jdbcTemplate.update(
"update tb_test1 set name=?,password=? where id = ?",
new PreparedStatementSetter(){
@Override
public void setValues(PreparedStatement ps) throws SQLException {
ps.setString(1, user.getUsername());
ps.setString(2, user.getPassword());
ps.setInt(3, user.getId());
}
});
}
删除:
@Override
public void delete(User user) {
Assert.isNull(user, "user is not null");
jdbcTemplate.update(
"delete from tb_test1 where id = ?",
new Object[]{user.getId()},
new int[]{java.sql.Types.INTEGER});
}
查询返回为基本类型:
@Deprecated //因为没有查询条件,所以用处不大
public int queryForInt1(){
return jdbcTemplate.queryForInt("select count(0) from tb_test1");
}
public int queryForInt2(User user){
return jdbcTemplate.queryForInt("select count(0) from tb_test1 where username = ?" ,
new Object[]{user.getUsername()});
}
//最全的参数3个
public int queryForInt3(User user){
return jdbcTemplate.queryForInt("select count(0) from tb_test1 where username = ?" ,
new Object[]{user.getUsername()},
new int[]{java.sql.Types.VARCHAR});
}
查询返回为String类型:
//可以返回是一个基本类型的值
@Deprecated //因为没有查询条件,所以用处不大
public String queryForObject1(User user) {
return (String) jdbcTemplate.queryForObject("select username from tb_test1 where id = 100",
String.class);
}
//返回值是一个对象的情形,下面的使用方法是错误的,查出一个非基本类型或String类型的对象不能使用queryObject方法
//因为queryObject方法是针对查出一个基本类型或String类型的。
@Deprecated //因为没有查询条件,所以用处不大
public User queryForObject2(User user) {
return (User) jdbcTemplate.queryForObject("select * from tb_test1 where id = 100", User.class); //class是结果数据的java类型
}
//经实验,下面的方法是可以使用的,如果想通过queryForObject方法查出一个非基本类型或String类型,只能加上RowMapper接口。
@Deprecated //因为没有查询条件,所以用处不大
public User queryForObject3(User user) {
return (User) jdbcTemplate.queryForObject("select * from tb_test1 where id = 100",
new RowMapper(){
@Override
public Object mapRow(ResultSet rs, int rowNum)throws SQLException {
User user = new User();
user.setId(rs.getInt("id"));
user.setUsername(rs.getString("username"));
user.setPassword(rs.getString("password"));
return user;
}
}
);
}
public User queryForObject5(User user) {
return (User) jdbcTemplate.queryForObject(
"select * from tb_test1 where id = ?",
new Object[]{user.getId()},
new RowMapper(){
@Override
public Object mapRow(ResultSet rs,int rowNum)throws SQLException {
User user = new User();
user.setId(rs.getInt("id"));
user.setUsername(rs.getString("username"));
user.setPassword(rs.getString("password"));
return user;
}
}); //class是结果数据的java类型
}
@Override
public User queryForObject(User user) {
//方法有返回值
return (User) jdbcTemplate.queryForObject("select * from tb_test1 where id = ?",
new Object[]{user.getId()},
new int[]{java.sql.Types.INTEGER},
new RowMapper() {
@Override
public Object mapRow(ResultSet rs, int rowNum) throws SQLException {
User user = new User();
user.setId(rs.getInt("1"));
user.setUsername(rs.getString("2"));
user.setPassword(rs.getString("3"));
return user;
}
}
);
}
查询返回为List:
@SuppressWarnings("unchecked")
public List queryForList1(User user) {
return (List) jdbcTemplate.queryForList("select * from tb_test1 where username = ?",
new Object[]{user.getUsername()},
User.class);
}
注意:上面的方法是错误的,查询多个非基本类型或String类型的对象时,用queryForList方法会报错,因为此方法是针对查多条基本类型或String类型的。下面的示例是可以的,因为它返回的是多个String类型的结果。
@SuppressWarnings("unchecked")
public List queryForList2(User user) {
return (List) jdbcTemplate.queryForList("select username from tb_test1 where sex = ?",
new Object[]{user.getSex()},
String.class);
}
查询使用RowCallbackHandler:
//通过RowCallbackHandler对Select语句得到的每行记录进行解析,并为其创建一个User数据对象。实现了手动的OR映射。
public User queryUserById4(String id){
final User user = new User();
//该方法返回值为void
this.jdbcTemplate.query("select * from tb_test1 where id = ?",
new Object[] { id },
new RowCallbackHandler() {
@Override
public void processRow(ResultSet rs) throws SQLException {
user.setId(rs.getInt("id"));
user.setUsername(rs.getString("username"));
user.setPassword(rs.getString("password"));
}
});
return user;
}
@SuppressWarnings("unchecked")
@Override
public List list(User user) {
return jdbcTemplate.query("select * from tb_test1 where username like '%?%'",
new Object[]{user.getUsername()},
new int[]{java.sql.Types.VARCHAR},
new RowMapper(){
@Override
public Object mapRow(ResultSet rs, int rowNum) throws SQLException {
User user = new User();
user.setId(rs.getInt("id"));
user.setUsername(rs.getString("username"));
user.setPassword(rs.getString("password"));
return user;
}
});
}
批量操作
//批量操作 适合于增、删、改操作
public int[] batchUpdate(final List users) {
int[] updateCounts = jdbcTemplate.batchUpdate(
"update tb_test1 set username = ?, password = ? where id = ?",
new BatchPreparedStatementSetter() {
@Override
public void setValues(PreparedStatement ps, int i) throws SQLException {
ps.setString(1, ((User)users.get(i)).getUsername());
ps.setString(2, ((User)users.get(i)).getPassword());
ps.setLong(3, ((User)users.get(i)).getId());
}
@Override
public int getBatchSize() {
return users.size();
}
}
);
return updateCounts;
}
调用存储过程:
//调用存储过程
public void callProcedure(int id){
this.jdbcTemplate.call("call SUPPORT.REFRESH_USERS_SUMMARY(?)", new Object[]{Long.valueOf(id)});
}
注意:queryForInt, queryForMap,queryForObject返回0行会抛异常的,也就是说查询不到结果的情况,自己已经证实。
Spring 中的 RowMapper
Spring中的RowMapper可以将数据中的每一行数据封装成用户定义的类.
我们在数据库查询中,如果返回的类型是用户自定义的类型(其实我们在数据库查询中大部分返回的都是自定义的类)则需要包装,如果是Java自定义的类型,如String则不需要.
如果Spring与Hibernate 相结合了,基本上是用不到,大多数都是在Spring单独使用时用到.
可以通过建立内部类实现RowMapper接口,RowMapper中有一个mapRow方法,所以实现RowMapper接口一定要实现mapRow方法,而对自定义类的包装就在mapRow方法中实现.
这里只是一个简单的例子:
public class TestDao {
private JdbcTemplate jt;
public void setJt(JdbcTemplate jt) {
this.jt = jt;
}
public List getAll(){
String sql = "select * from t_npc";
//使用
List list = jt.query(sql, new NpcRowMapper());
return list;
}
/**
* 定义内部类实现RowMapper接口
*/
public class NpcRowMapper implements RowMapper{
//实现mapRow方法
public Object mapRow(ResultSet rs, int num) throws SQLException {
//对类进行封装
TNpc npc = new TNpc();
npc.setId(rs.getLong("id"));
npc.setName(rs.getString("name"));
return npc;
}
}
}
上面提及到可能会遇到查询不到结果的情况,会抛异常,因此,我们需要在异常处理时注意这个问题。在finnally语句块中进行处理。
try
{
messagebox.show("true");
}
catch
{
messagebox.show("false");
}
finally
{
messagebox.show("finally");
}
try
{
//执行的代码,其中可能有异常。一旦发现异常,则立即跳到catch执行。否则不会执行catch里面的内容
}
catch
{
//除非try里面执行代码发生了异常,否则这里的代码不会执行
}
finally
{
//不管什么情况都会执行,包括try catch 里面用了return ,可以理解为只要执行了try或者catch,就一定会执行 finally
}
总结:
JdbcTemplate是Spring框架自带的对JDBC操作的封装,目的是提供统一的模板方法,使得对数据库的操作更加方便、友好,效率也不错。但是功能还是不够强大(比如不支持级联属性),在实际应用中还需要和hibernate、mybaties等框架混合使用。