07-Spring与JdbcTemplate
Spring与JDBC模板
为了避免直接使用JDBC而带来的复杂且冗余的代码,Spring提供了一个强有力的模板类--JdbcTemplate来简化JDBC操作。并且,数据源DataSource对象与模板JdbcTemplate对象均可通过Bean的形式定义在配置文件中,充分发挥了依赖注入的威力。
Spring提供了很多持久层技术的模板类简化编程
1. 数据源的配置
使用JDBC模板,首先需要配置好数据源,数据源直接以Bean的形式配置在Spring配置文件中,根据数据源的不同,其配置方式不同。下面主要讲解三种常用数据源的配置方式
(1)Spring默认的数据源
(2)C3P0数据源
(3)DBCP数据源
- Spring默认的数据源DriverManagerDataSource
Spring默认的数据源是DriverManagerDataSource,其中有一个属性,DriverClassName,用于接受DB驱动。DriverManagerDataSource类继承自AbstractDriverBasedDataSource。其有三个属性用于接受连接数据库的URL、用户名与密码。
<bean id="myDataSource" class="org.springframework.jdbc.dataSource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost:3306/test" />
<property name="username" value="root" />
<property name="password" value="123" />
</bean>
- C3P0数据源【ComboPooledDataSource】
使用C3P0数据源,需要导入一个JAR包,在Spring依赖库的解压目录的com.mchange.C3P0目录。
C3P0数据源是CombopooledDataSource,Ctrl+O查看类结构可看到,其有DriverClass,jdbcUrl,user,password四个DB连接属性。
<bean id="myC3P0DataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="com.mysql.jdbc.Driver" />
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/test" />
<property name="user" value="root" />
<property name="password" value="123" />
</bean>
- DBCP数据源【BasicDataSource】
DBCP,DataBase Connection Pool,是Apache下的项目,使用该数据源,需要导入两个JAR包,它们在Spring依赖库的org.apach.commons目录中的dbcp和pool子包中。
DBCP数据源是BasicDataSource,Ctrl+o查看类结构,其中有driverClassName,url,username,password四个DB连接属性。
<bean id="myDBCPDataSource" class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost:3306/test" />
<property name="username" value="root" />
<property name="password" value="123" />
</bean>
- 从属性文件读取数据库连接信息
为了便于维护,可以将数据库连接信息写入到属性文件中,使Spring配置文件从中读取数据。
属性文件名称随意,但一般都是放在src下。
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql:///test
jdbc.user=root
jdbc.password=123
Spring配置文件从属性文件中读取数据时,需要在
<!--配置C3P0数据源-->
<bean id="myC3P0DataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${jdbc.driver}" />
<property name="jdbcUrl" value="${jdbc.url}" />
<property name="user" value="${jdbc.user}" />
<property name="password" value="${jdbc.password}" />
</bean>
该属性文件若要被Spring配置文件读取,其必须在配置文件中进行注册。注册方式有两种:
(1)
以PropertyPlaceholderConfigurer类的bean实例的方式进行注册,该类有一个属性location,用于指定属性文件的位置,这种方式不常用。
<!--注册属性文件方式一:-->
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="location" value="classpath:jdbc.properties" />
</bean>
(2)context:property-placeholder/方式
该方式要求在Spring配置文件头部加入context的约束,即修改配置文件头。
context:property-placeholder/标签中有一个属性location,用于指定属性文件的位置。
<!--注册属性文件方式二:-->
<context:property-placeholder location="classpath:jdbc.properties" />
2. 配置JDBC模板
JDBC模板类jdbcTemplate从其父类JdbcAccessor继承了一个属性dataSourcce,用于接受数据源。
查看JdbcTemplate源码,及JdbcAccessor的类结构
<!--配置JDBC模板-->
<bean id="myJdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="myC3P0DataSource"/>
</bean>
3. Dao实现类继承JdbcDaoSupport类
JdbcDaoSupport类中有一个属性JdbcTemplate,用于接收JDBC模板,所以Dao实现类继承了JdbcDaoSupport类后,也就具有了JDBC模板属性,在配置文件中,只要将模板对象注入即可。
<!--配置JDBC模板-->
<bean id="myJdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="myC3P0DataSource" />
</bean>
<bean id="myDao" class="com.zhy.dao.UserDaoImpl">
<property name="jdbcTempldate" ref="myJdbcTemplate" />
</bean>
再仔细查看JdbcDaoSupport类,发现其有一个dataSource属性,查看setDataSource()方法体可知,若JDBC模板为null,则会自动创建一个模板对象。故,在Spring配置文件中,对于JDBC模板对象的配置完全可以省去,而是在Dao实现类中直接注入数据源对象。这样会让系统自动创建JDBC模板对象。
public final void setDataSource(DataSource dataSource){
if(this.jdbcTemplate == null || dataSource != this.jdbcTemplate.getDataSouce()){
this.jdbcTemplate = createJdbcTemplate(dataSource);
initTemplateConfig();
}
}
<!--配置DAO-->
<bean id="myDao" class="com.zhy.dao.UserDaoImpl">
<property name="dataSource" ref="myC3P0DataSource"/>
</bean>
4. 对DB的增、删、改操作
JdbcTemplate类中提供了对DB进行修改、查询的方法。Dao实现类使用继承自JdbcDaoSupport的getTemplate()方法,可以获取到JDBC模板对象。
对DB的增、删、改都是通过update()方法实现的。该方法常用的重载方法有两个:
public int update(String sql)
public int update(String sql,Object... args)
public int[] batchUpdate(String sql,List<Object[]> batchArgs):批量更新
第一个参数为要执行的SQL语句,第二个参数为要执行的SQL语句中所包含的动态参数。其返回值为所影响的记录的条数,一般不用。
public interface IUserDao{
void insertUser(User user);
void deleteUser(int id);
void updateUser(User user);
String selectUsernameById(int id);
List<String> selectUsernamesByAge(int age);
User selectUserById(int id);
List<User> selectAllUsers();
// 执行批量更新
void testBatchUpdate();
}
public class UserDaoImpl extends JdbcDaoSupport implements IUserDao{
@Override
public void insertUser(User user){
String sql = "insert into user(username,age) values(?,?)";
this.getJdbcTemplate().update(sql,user.getUsername(),user.getAge());
}
@Override
public void deleteUser(int id){
String sql = "delete from user where id=?";
this.getJdbcTemplate().update(sql,id);
}
@Override
public void updateUser(User user){
String sql = "update user set username=?,age=? where id=?";
this.getJdbcTemplate().update(sql,user.getUsername(),user.getAge(),user.getId());
}
@Override
public void insertUser(User user){
String sql = "insert into user(username,age) values(?,?)";
this.getJdbcTemplate().update(sql,user.getUsername(),user.getAge());
}
@Override
public void testBatchUpdate(){
String sql = "insert into user(username,age) values(?,?)";
List<Object[]> batchArgs = new ArrayList<>();
batchAegs.add(new Object[]{"AA"},21);
batchAegs.add(new Object[]{"BB"},22);
batchAegs.add(new Object[]{"CC"},23);
batchAegs.add(new Object[]{"DD"},24);
this.getJdbcTemplate().batchUpdate(sql,batchArgs);
}
}
5. 对DB的查询操作
JDBC模板的查询结果均是以对象的形式返回。根据返回对象类型的不同,可以将查询分为两类:简单对象的查询,与自定义对象查询。
简单对象查询:查询结果为String、Integer等简单对象类型,或该类型作为元素的集合类型,如List
自定义对象查询:查询结果为自定义类型,如User等,或该类型作为元素的集合类型,如List
(1)简单对象查询
常用的简单对象查询方法有:查询结果为单个对象的queryForObject()与查询结果为List的queryForList()。
public T queryForObject(String sql,Class
type,Object... args)
public ListqueryForList(String sql,Class type,Object... args)
@Override
public String selectUsernameById(int id){
String sql = "select username from user where id=?";
String username = this.getJdbcTemplate().queryForObject(sql,String.class,id);
return username;
}
@Override
public List<String> selectUsernameByAge(int age){
String sql="select username from user hwere age=?";
List<String> allUsernames = this.getJdbcTemplate().queryForList(sql,String.class,age);
return allUsernames;
}
(2)自定义对象查询
常用的自定义对象查询方法有:查询结果为单个对象的queryForObject()与查询结果为List的query()。
public T queryForObject(String sql,RowMapper
m,Object... args)
public Listquery(String sql,RowMapper m,Object... atgs)
注意:RowMapper为记录映射接口,用于将查询结果集中每一条记录包装为指定对象。该接口中有一个方法需要实现:
public Object mapRow(ResultSet rs,int rowNum)
参数rowRum表示总的结果集中当前行的行号,但参数rs并不表示总的结果集,而是表示rowNum所表示所代表的当前行的记录所定义的结果集,仅仅是当前行的结果。
一般,该方法体中就是实现将查询结果中当前行的数据包装为一个指定对象。
private class UserRowMapper implments RowMapper<User>{
@Override
public User mapRow(ResultSet rs,int rowNum) throws SQLException{
User user = new User();
user.setId(rs.getInt("id"));
user.setUsername(rs.getString("username"));
user.setAge(rs.getInt("age"));
return user;
}
}
@Override
public List<User> selectAllUsers(){
String sql = "select * from user";
List<User> users = this.getJdbcTemplate().query(sql,new UserRowMapper());
return users;
}
@Override
public User selectUserById(int id){
String sql = "select * from user where id=?";
User user = this.getJdbcTemplate().queryForObject(sql,new UserRowMapper(),id);
return user;
}
6. 注意:JDBC模板对象是多例的
JdbcTemplate对象是多例的,即系统会为每一个使用模板对象的线程(方法)创建一个JdbcTemplate实例,并且在该线程(方法)结束时,自动释放JdbcTemplate实例。所以在每次使用JdbcTemplate对象时,都需要通过getJdbcTemplate()方法获取。
7. JDBC模板的使用步骤
(1) 导入JAR包
除了Spring的基本JAR包,数据库驱动包外,还需要导入两个JAR包,它们均在Spring框架解压目录下的libs目录中。分别是Spring的JDBC JAR包,还有就是Spring的事务JAR包。
(2) 定义数据库及表
(3) 创建一个测试类
@Test
public void demo(){
// DriverManagerDataSource是Spring的默认数据源。
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql:///spring");
dataSource.setUsername("root");
dataSource.setPassword("123");
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
jdbcTemplate.update("insert into account values(null,?,?)","会",100d);
}
(4) 书写DAO层
public class User{
private Integer id;
private String name;
// setter and getter
}
public interface UserDao{
// 增
void save(User u);
// 删
void delete(Integer id);
// 该
void update(User u);
// 查
User getById(Integer id);
// 查
int getTotalCount();
// 查
List<User> getAll();
}
(5) 编写实现类
// 使用JDBC模板实现增删改操作
public class UserDaoImpl implements UserDao{
private JdbcTemplate jt;
@Override
public void save(User u){
String sql="insert into t_user values(null,?)";
jt.update(sql,u.getName(),u.getId());
}
@Override
public void delete(Integer id){
String sql="delete from t_user where id=?";
jt.update(sql,id);
}
@Override
public void update(User u){
String sql="update t_user set name=? where id=?";
jt.update(sql,u.getName(),u.getId());
}
@Override
public User getById(Integer id){
String sql="select * from t_user where id=?";
return jt.queryForObject(sql,new RowMapper<User>(){
@Override
public User mapRow(ResultSet rs,int args)throws SQLException{
User u = new User();
u.setId(rs.getInt("id"));
u.setName(rs.getString("name"));
return u;
}
},id);
}
@Override
public int getTotalCount(){
String sql="select count(*) from t_user";
Integer count = jt.queryForObject(sql,Integer.class);
return count;
}
@Override
public List<User> getAll(){
String sql="select * from t_user";
List<User> list = jt.query(sql,new RowMapper<User>(){
@Override
public User mapRow(ResultSet rs,int args) throws SQLException{
User u = new User();
u.setId(rs.getInt("id"));
u.setName(rs.getString("name"));
return u;
}
});
return list;
}
}
查询单个对象
查询聚合函数【单个值】
查询list集合类型
(6) Spring的配置依赖关系
<!--1.将连接池放入Spring容器-->
<bean name="dataSource" class="">
<property name="jdbcUrl" value=""></property>
<property name="driverClass" value=""></property>
<property name="user" value=""></property>
<property name="password" value=""></property>
</bean>
<!--2.将JdbcTemplate放入Spring容器-->
<bean name="jdbcTemplate" class="">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--3.将UserDao放入Spring容器-->
<bean name="userDao" class="">
<property name="jt" ref="jdbcTemplate"></property>
</bean>
(7) 定义测试类MyTest
//演示JDBC模板
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class Demo {
@Resource(name="userDao")
private UserDao ud;
@Test
public void fun1() throws Exception{
//0 准备连接池
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setDriverClass("com.mysql.jdbc.Driver");
dataSource.setJdbcUrl("jdbc:mysql:///hibernate_32");
dataSource.setUser("root");
dataSource.setPassword("1234");
//1 创建JDBC模板对象
JdbcTemplate jt = new JdbcTemplate();
jt.setDataSource(dataSource);
//2 书写sql,并执行
String sql = "insert into t_user values(null,'rose') ";
jt.update(sql);
}
@Test
public void fun2() throws Exception{
User u = new User();
u.setName("tom");
ud.save(u);
}
@Test
public void fun3() throws Exception{
User u = new User();
u.setId(2);
u.setName("jack");
ud.update(u);
}
@Test
public void fun4() throws Exception{
ud.delete(2);
}
@Test
public void fun5() throws Exception{
System.out.println(ud.getTotalCount());
}
@Test
public void fun6() throws Exception{
System.out.println(ud.getById(1));
}
@Test
public void fun7() throws Exception{
System.out.println(ud.getAll());
}
}
8. 进阶内容
*(1)JDBCDaoSupport
*(2)读取外部的Properties配置
注意加前缀
*(3)NamedParameterJdbcTemplate具名参数
在经典的JDBC用法中,SQL参数是用占位符?表示,并且受到位置的限制,定位参数的问题在于,一旦参数的顺序发生变化,就必须改变参数绑定。
在Spring JDBC框架中,绑定SQL参数的另一种选择是使用具命参数【named parameter】
具名参数:SQL按名称(以冒号开头)而不是按位置进行指定,具名参数更易于维护,也提升了可读性。具名参数由框架类在运行时用占位符取代。
具名参数只在NamedParameterJdbcTemplate中得到支持。
在SQL语句中使用具名参数时,可以在一个Map中提供参数值,参数名为键。也可以使用SqlParameterSource参数。批量更新时可以提供Map或SqlParameterSource的数组
- Map
好处:若有很多个参数,则不用再去对应位置,直接对应参数名,便于维护。
缺点:较为麻烦。 - SqlParameterSource
好处:SQL语句中的参数名和类的属性一致。
缺点:使用SqlParameterSource的BeanPropertySqlParamterSource实现类作为参数。
<!--配置NamedParameterJdbcTemplate,该对象可以使用具名参数,其没有无参数的构造器,所以必须为其构造器指定参数-->
<bean id="namedParameterJdbcTemplate" class="NamedParameterJdbcTemplate">
<constructor-arg ref="dataSource" />
</bean>
private NamedParameterJdbcTemplate namedParameterJdbcTemplate;
@Before
public void init(){
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
namedParameterJdbcTemplate = ac.getBean(NamedParameterJdbcTemplate.class);
}
@Test
public void testNamedParamterJdbcTemplate1(){
String sql = "insert into employees (last_name,email,dept_id) values (:lastName,:email,:deptId)";
Map<String,Object> paramMap = new HashMap<>();
paramMap.put("lastName","FF");
paramMap.put("email","ff@163.com");
paramMap.put("deptId",2);
namedParameterJdbcTemplate.update(sql,paramMap);
}
@Test
public void testNamedParamterJdbcTemplate2(){
String sql = "insert into employees (last_name,email,dept_id) values (:lastName,:email,:deptId)";
Employee employee = new Employee();
employee.setLastName("XYZ");
employee.setEmail("xyz.@sina.com");
employee.setDeptId(3);
SqlParameterSource paramSource = new BeanPropertySqlParamterSource(employee);
namedParameterJdbcTemplate.update(sql,paramSource);
}