此前入门介绍Spring的时候,有提到过Spring是一个“一站式”框架,即Spring在JavaEE的三层架构[表现层(Web层)、业务逻辑层(Service层)、数据访问层(DAO层)]中,每一层均提供了不同的解决技术。那么本文将讲解的是Spring对DAO层的技术支持。
Spring对不同的持久化技术提供了对应的简单操作的模板和回调。如下:
ORM持久化技术 | 模板类 |
---|---|
JDBC | org.springframework.jdbc.core.JdbcTemplate |
Hibernate5.0 | org.springframework.orm.hibernate5.HibernateTemplate |
IBatis(MyBatis) | org.springframework.ibatis.SqlMapClientTemplate |
JPA | org.springframework.orm.jpa.JpaTemplate |
下面将讲解的是Spring对JDBC提供的模板JdbcTemplate的使用。通过简单的案例进行学习。
创建一个新的工程,导入相关jar包,除包括Spring基础jar包外,还需要导入JDBC模板开发包和对应的数据库驱动,此外为了方便测试还需引入junit相关的jar包,包括如下:
Spring基础jar包:
1. spring-beans
2. spring-context
3. spring-core
4. spring-expression
5. commons-logging-1.2.jar
6. log4j-1.2.17.jar
Spring JDBC模板开发包:
1. spring-jdbc
2. spring-tx
MySQL数据库驱动jar:
1. mysql-connector-java-5.1.46.jar
junit相关的jar包:
1. junit-4.12.jar
2. hamcrest-core-1.3.jar
案例:这里以User为例,将User对象中的属性对应保存到数据中。
(1)首先定义User类,如下:
package com.wm103.jdbc.dao;
/**
* Created by DreamBoy on 2018/3/24.
*/
public class User {
private int id;
private String username;
private String password;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", password='" + password + '\'' +
'}';
}
}
(2)创建对应User的Dao层接口,如下:
package com.wm103.jdbc.dao;
/**
* Created by DreamBoy on 2018/3/24.
*/
public interface IUserDao {
int add(User user);
}
(3)创建Dao层的接口实现类,并实现IUserDao接口,如下:
package com.wm103.jdbc.dao;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
/**
* Created by DreamBoy on 2018/3/24.
*/
public class UserDaoImpl implements IUserDao {
private DriverManagerDataSource dataSource;
private JdbcTemplate jdbcTemplate;
public UserDaoImpl() {
initDatabase();
}
private void initDatabase() {
// 设置数据库信息
dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql:///mydb_329?useSSL=false");
dataSource.setUsername("root");
dataSource.setPassword("");
// 创建JdbcTemplate对象,设置数据源
jdbcTemplate = new JdbcTemplate(dataSource);
}
@Override
public int add(User user) {
System.out.println("start add method...");
String sql = "insert into user(username, password) values(?, ?)";
int rows = jdbcTemplate.update(sql, user.getUsername(), user.getPassword());
System.out.println("method result: " + rows);
return rows;
}
}
为了方便起见,在该实现类中,我们定义了初始化数据库信息的方法initDatabase
,在该方法中通过DriverManagerDataSource
类设置数据库信息,使用该类的对象创建JdbcTemplate
对象,设置数据源。在add
方法中使用jdbcTemplate
对象的update
方法实现添加的操作,结果返回的是受影响的行数。
(4)定义该项目的Spring核心配置文件,这里将该文件命名为bean1.xml。内容如下:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="userDaoImpl" class="com.wm103.jdbc.dao.UserDaoImpl"/>
beans>
(5)最后我们来创建一个测试类TestJdbc,用于测试添加操作。TestJdbc.java内容如下:
package com.wm103.jdbc;
import com.wm103.jdbc.dao.User;
import com.wm103.jdbc.dao.UserDaoImpl;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* Created by DreamBoy on 2018/3/24.
*/
public class TestJdbc {
UserDaoImpl userDaoImpl;
@Before
public void init() {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("bean1.xml");
UserDaoImpl userDaoImpl = (UserDaoImpl) applicationContext.getBean("userDaoImpl");
this.userDaoImpl = userDaoImpl;
}
@Test
public void runUserAdd() {
User user = new User();
user.setUsername("spring-jdbc");
user.setPassword("spring-password");
userDaoImpl.add(user);
}
}
(6)通过以上简单案例,我们使用Spring中的JdbcTemplate(即对JDBC提供的封装)实现了添加操作。但在该案例中实际上存在着一个问题,即DriverManagerDataSource
对象在UserDaoImpl类中被创建,无法为Dao层中其他的操作数据库的实现使用,除此之外,DriverManagerDataSource
底层获取数据库连接是通过DriverManager.getConnection
获取,每次调用DriverManagerDataSource
的getConnection
获取对数据库的连接,都相当于创建一个新的连接,这种方式下耗费内存和时间,实用性低。我们需要一个数据库连接池,能有效地负责创建、管理和分配数据库连接。为了方便对Spring的JdbcTemplate进行讲解,仍采用这种形式创建数据源。后续将介绍c3p0连接池的使用(c3p0实现了DataSource
接口,维护了数据库连接,负责创建、管理和分配数据库连接)。
案例:以根据用户ID更新密码为例,实现更新操作。
(1)在接口IUserDao中,增加:
int setPasswordById(int userId, String password);
(2)在UserDaoImpl实现类中实现该方法,即实现更新操作,如下:
@Override
public int setPasswordById(int id, String password) {
System.out.println("start setPasswordById method...");
String sql = "UPDATE user SET password = ? WHERE id = ?";
int rows = jdbcTemplate.update(sql, password, id);
System.out.println("method result: " + rows);
return rows;
}
(3)在TestJdbc测试类中添加测试方法,如下:
@Test
public void runUserSetPasswordById() {
userDaoImpl.setPasswordById(9, "spring-password22333");
}
案例:根据用户ID删除用户记录的操作。
(1)在接口IUserDao中,增加:
int delete(int id);
(2)在UserDaoImpl实现类中实现该方法,即实现删除操作,如下:
@Override
public int delete(int id) {
System.out.println("start delete method...");
String sql = "DELETE FROM user WHERE id = ?";
int rows = jdbcTemplate.update(sql, id);
System.out.println("method result: " + rows);
return rows;
}
(3)在TestJdbc测试类中添加测试方法,如下:
@Test
public void runUserDelete() {
userDaoImpl.delete(9);
}
案例:获取user表中的记录数。
实现:调用JdbcTemplate
对象的queryForObject
方法。
(1)在UserDaoImpl实现类中实现该方法,如下:
public int getCountNum() {
System.out.println("start getCountNum method...");
String sql = "SELECT count(*) FROM user";
int count = jdbcTemplate.queryForObject(sql, Integer.class); // 参数:SQL语句+返回类型的class
System.out.println("method result: " + count);
return count;
}
(2)在TestJdbc测试类中添加测试方法,如下:
@Test
public void runUserGetCountNum() {
int count = userDaoImpl.getCountNum();
System.out.println("TestJdbc User Count: " + count);
}
案例:根据用户ID获取对应的用户记录信息。
实现:调用JdbcTemplate
对象的queryForObject
方法,结果查询结果为一个对象,要求queryForObject
方法的第二个参数传入一个实现了RowMapper
接口的实现类(实现自己数据的封装)。
(1)在UserDaoImpl实现类中实现该方法,如下:
@Override
public User get(int id) {
String sql = "SELECT * FROM user WHERE id = ?";
User user = jdbcTemplate.queryForObject(sql, new UserRowMapper(), id);
return user;
}
(2)并在UserDaoImpl类中定义一个RowMapper
接口的内部实现类,其作用是将查询结果封装为某一自定义对象后返回,如下:
class UserRowMapper implements RowMapper<User> {
@Override
public User mapRow(ResultSet resultSet, int i) throws SQLException {
// 1. 从结果集中取出数据
int id = resultSet.getInt("id");
String username = resultSet.getString("username");
String password = resultSet.getString("password");
// 2. 将数据封装到对象中
User user = new User();
user.setId(id);
user.setUsername(username);
user.setPassword(password);
return user;
}
}
(3)在TestJdbc测试类中添加测试方法,如下:
@Test
public void runUserGet() {
int id = 10;
User user = userDaoImpl.get(id);
System.out.println(user);
}
案例:获取user表中的所有用户信息记录。
实现:调用JdbcTemplate
对象的query
方法,结果查询结果为一个List集合,要求query
方法的第二个参数,要求传入一个实现了RowMapper
接口的实现类(实现自己数据的封装)。
(1)在UserDaoImpl实现类中实现该方法,如下:
@Override
public List getAll() {
String sql = "SELECT * FROM user";
return jdbcTemplate.query(sql, new UserRowMapper());
}
(采用的RowMapper
接口实现类仍为上述创建的UserRowMapper
)
(2)在TestJdbc测试类中添加测试方法,如下:
@Test
public void runUserGetAll() {
List userList = userDaoImpl.getAll();
System.out.println(userList);
}
在上述中提及了使用DriverManagerDataSource
存在的问题,即DriverManagerDataSource
未对创建的数据库连接进行有效管理,对于每一次获取数据库连接(即DriverManager.getConnection
)都会新建新的数据库连接,这样的做法对耗费内存和时间,实用性低且这种方式获取的连接需要手动关闭,不然会大量的占用内存。
那么为对数据库连接进行有效管理,可以使用c3p0连接池,即它会帮我们考虑初始创建的数据库连接数,如何分配数据库连接,以及关闭数据库连接后connection对象是放回池内,还是close销毁等问题。对于连接池的实现,均要求实现了DataSource接口(DriverManagerDataSource
也是实现了该接口)。DataSource接口,内容如下:
/*
* Copyright (c) 2000, 2013, Oracle and/or its affiliates. All rights reserved.
* ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
*/
package javax.sql;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Wrapper;
/**
* A factory for connections to the physical data source that this
* {@code DataSource} object represents. An alternative to the
* {@code DriverManager} facility, a {@code DataSource} object
* is the preferred means of getting a connection. An object that implements
* the {@code DataSource} interface will typically be
* registered with a naming service based on the
* Java™ Naming and Directory (JNDI) API.
*
* The {@code DataSource} interface is implemented by a driver vendor.
* There are three types of implementations:
*
* - Basic implementation -- produces a standard {@code Connection}
* object
*
- Connection pooling implementation -- produces a {@code Connection}
* object that will automatically participate in connection pooling. This
* implementation works with a middle-tier connection pooling manager.
*
- Distributed transaction implementation -- produces a
* {@code Connection} object that may be used for distributed
* transactions and almost always participates in connection pooling.
* This implementation works with a middle-tier
* transaction manager and almost always with a connection
* pooling manager.
*
*
* A {@code DataSource} object has properties that can be modified
* when necessary. For example, if the data source is moved to a different
* server, the property for the server can be changed. The benefit is that
* because the data source's properties can be changed, any code accessing
* that data source does not need to be changed.
*
* A driver that is accessed via a {@code DataSource} object does not
* register itself with the {@code DriverManager}. Rather, a
* {@code DataSource} object is retrieved though a lookup operation
* and then used to create a {@code Connection} object. With a basic
* implementation, the connection obtained through a {@code DataSource}
* object is identical to a connection obtained through the
* {@code DriverManager} facility.
*
* An implementation of {@code DataSource} must include a public no-arg
* constructor.
*
* @since 1.4
*/
public interface DataSource extends CommonDataSource, Wrapper {
/**
*
Attempts to establish a connection with the data source that
* this {@code DataSource} object represents.
*
* @return a connection to the data source
* @exception SQLException if a database access error occurs
* @throws java.sql.SQLTimeoutException when the driver has determined that the
* timeout value specified by the {@code setLoginTimeout} method
* has been exceeded and has at least tried to cancel the
* current database connection attempt
*/
Connection getConnection() throws SQLException;
/**
*
Attempts to establish a connection with the data source that
* this {@code DataSource} object represents.
*
* @param username the database user on whose behalf the connection is
* being made
* @param password the user's password
* @return a connection to the data source
* @exception SQLException if a database access error occurs
* @throws java.sql.SQLTimeoutException when the driver has determined that the
* timeout value specified by the {@code setLoginTimeout} method
* has been exceeded and has at least tried to cancel the
* current database connection attempt
* @since 1.4
*/
Connection getConnection(String username, String password)
throws SQLException;
}
即实际上实现DataSource接口的具体实现类均实现了如何获取连接的方法(getConnection
),类所暴露出来了方法,隐藏了如何获取连接的细节。
下面通过一个案例来看看,c3p0连接池是如何使用的吧。
这里以添加用户信息到数据库中为例。
(1)首先导入jar包(除上述提到的jar包,还需导入),即c3p0-0.9.2.1.jar
和mchange-commons-java-0.2.3.4.jar
(c3p0 jar包的下载,可以到这里进行搜索下载。)
(2)创建UserDao类,如下:
package com.wm103.c3p0;
import org.springframework.jdbc.core.JdbcTemplate;
/**
* Created by DreamBoy on 2018/4/1.
*/
public class UserDao {
private JdbcTemplate jdbcTemplate;
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
public int add(User user) {
return 0;
}
}
其中这里的User类同上述提到的User类内容一致。此外,还设置了JdbcTemplate属性,以及对应的setter方法,为后续使用JdbcTemplate实现添加操作。
(3)创建UserService类,并使用UserDao类的add方法,实现add操作,如下:
package com.wm103.c3p0;
/**
* Created by DreamBoy on 2018/4/1.
*/
public class UserService {
private UserDao userDao;
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
public int add(User user) {
return userDao.add(user);
}
}
(4)创建Spring核心配置文件,并在配置文件中配置c3p0连接池;创建JdbcTemplate,注入数据源;创建UserService,注入UserDao;创建UserDao,注入JdbcTemplate,如下:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="com.mysql.jdbc.Driver"/>
<property name="jdbcUrl" value="jdbc:mysql:///mydb_329"/>
<property name="user" value="root"/>
<property name="password" value=""/>
bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource">property>
bean>
<bean id="userService" class="com.wm103.c3p0.UserService">
<property name="userDao" ref="userDao">property>
bean>
<bean id="userDao" class="com.wm103.c3p0.UserDao">
<property name="jdbcTemplate" ref="jdbcTemplate">property>
bean>
beans>
(5)使用JdbcTemplate对象实现UserDao中的add方法,如下:
public int add(User user) {
String sql = "INSERT INTO user(username, password) VALUES(?, ?)";
return jdbcTemplate.update(sql, user.getUsername(), user.getPassword());
}
(6)创建TestC3p0测试类,测试UserService的add操作,如下:
package com.wm103.c3p0;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* Created by DreamBoy on 2018/4/1.
*/
public class TestC3p0 {
@Test
public void runC3p0() {
ApplicationContext context = new ClassPathXmlApplicationContext("bean2.xml");
UserService userService = (UserService) context.getBean("userService");
User user = new User();
user.setUsername("spring-c3p0");
user.setPassword("c3p0-233333");
userService.add(user);
}
}
(7)对上述案例进行修改,修改内容如下:
1. 属性注入采用注解方式(因此,还需要导入spring-aop这个jar包);
2. 对数据库的配置信息采用db.properties文件进行保存,在Spring配置文件中进行导入。
(8)注解方式注入属性,修改内容如下:
UserService.java
@Resource(name="userDao")
private UserDao userDao;
UserDao.java
@Resource(name="jdbcTemplate")
private JdbcTemplate jdbcTemplate;
(9)在src目录下创建db.properties文件,内容如下:
jdbc.driverClass=com.mysql.jdbc.Driver
jdbc.jdbcUrl=jdbc:mysql:///mydb_329
jdbc.user=root
jdbc.password=
(10)创建新的Spring核心配置文件bean3.xml,开启注解扫描,以及导入db.properties文件内容信息,如下:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="com.wm103.c3p0">context:component-scan>
<context:property-placeholder location="classpath:db.properties"/>
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${jdbc.driverClass}"/>
<property name="jdbcUrl" value="${jdbc.jdbcUrl}"/>
<property name="user" value="${jdbc.user}"/>
<property name="password" value="${jdbc.password}"/>
bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource">property>
bean>
<bean id="userService" class="com.wm103.c3p0.UserService">bean>
<bean id="userDao" class="com.wm103.c3p0.UserDao">bean>
beans>
(11)在测试类TestC3p0中,新增测试方法,内容如下:
@Test
public void runC3p02() {
ApplicationContext context = new ClassPathXmlApplicationContext("bean3.xml");
UserService userService = (UserService) context.getBean("userService");
User user = new User();
user.setUsername("spring-c3p03");
user.setPassword("c3p0-233333-2");
userService.add(user);
}