Spring Boot 中使用Spring的JdbcTemplate操纵数据

为了使 JDBC 更加易于使用,Spring 在JDBC的API 上定义了一个抽象层, 以此建立了一个JDBC存取框架。

作为 Spring JDBC 框架的核心, JDBC 模板的设计目的是为不同类型的JDBC操作提供模板方法。每个模板方法都能控制整个过程,并允许覆盖过程中的特定任务。通过这种方式,可以在尽可能保留灵活性的情况下,将数据库存取的工作量降到最低。

JdbcTemplate主要提供以下五类方法:

  • execute方法:可以用于执行任何SQL语句,一般用于执行DDL语句
  • update方法及batchUpdate方法:update方法用于执行新增、修改、删除等语句
  • batchUpdate方法用于执行批处理相关语句
  • query方法及queryForXXX方法:用于执行查询相关语句
  • call方法:用于执行存储过程、函数相关语句

使用示例:
在数据库中先准备两张表:

department表
employees表

在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 batchArgs = new ArrayList<>();  
          
        batchArgs.add(new Object[]{"AA", "[email protected]", 1});  
        batchArgs.add(new Object[]{"BB", "[email protected]", 2});  
        batchArgs.add(new Object[]{"CC", "[email protected]", 3});  
        batchArgs.add(new Object[]{"DD", "[email protected]", 3});  
        batchArgs.add(new Object[]{"EE", "[email protected]", 2});  
          
        jdbcTemplate.batchUpdate(sql, batchArgs);  
    }  
      
    /** 
     * 从数据库中获取一条记录,实际得到对应的一个对象 
     * 注意:不是调用 queryForObject(String sql, Class requiredType, Object... args) 方法! 
     * 而需要调用 queryForObject(String sql, RowMapper rowMapper, Object... args) 
     * 1、其中的 RowMapper 指定如何去映射结果集的行,常用的实现类为 BeanPropertyRowMapper 
     * 2、使用 SQL中的列的别名完成列名和类的属性名的映射,例如 last_name lastName 
     * 3、不支持级联属性。 JdbcTemplate 只能作为一个 JDBC 的小工具, 而不是 ORM 框架 
     */  
    @Test  
    public void testQueryForObject() {  
        String sql = "SELECT id, last_name lastName,email,dept_id as \"department.id\" FROM employees WHERE ID = ?";  
        RowMapper rowMapper = new BeanPropertyRowMapper<>(Employee.class);  
        //在将数据装入对象时需要调用set方法。  
        Employee employee = jdbcTemplate.queryForObject(sql, rowMapper, 1);  
          
        System.out.println(employee);  
    }  
      
    /** 
     * 一次查询多个对象 
     * 注意:调用的不是 queryForList 方法 
     */  
    @Test  
    public void testQueryForList() {  
        String sql = "SELECT id, last_name lastName, email FROM employees WHERE id > ?";  
        RowMapper rowMapper = new BeanPropertyRowMapper<>(Employee.class);  
        List employees = jdbcTemplate.query(sql, rowMapper,5);  
          
        System.out.println(employees);  
    }  
    /** 
     * 获取单个列的值或做统计查询 
     * 使用 queryForObject(String sql, Class requiredType)  
     */  
    @Test  
    public void testQueryForObject2() {  
        String sql = "SELECT count(id) FROM employees";  
        long count = jdbcTemplate.queryForObject(sql, Long.class);  
          
        System.out.println(count);  
    }     
}  

在实际的使用中,一般会创建一个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等框架混合使用。

你可能感兴趣的:(Spring Boot 中使用Spring的JdbcTemplate操纵数据)