Spring框架自學之路——JdbcTemplate
目錄
介紹
此前入門介紹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的使用。通過簡單的案例進行學習。
使用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。內容如下:
<?xml version="1.0" encoding="UTF-8"?>
<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);
}
返回List集合(返回多行資料)
案例:獲取user表中的所有使用者資訊記錄。
實現:呼叫JdbcTemplate
物件的query
方法,結果查詢結果為一個List集合,要求query
方法的第二個引數,要求傳入一個實現了RowMapper
介面的實現類(實現自己資料的封裝)。
(1)在UserDaoImpl實現類中實現該方法,如下:
@Override
public List<User> getAll() {
String sql = "SELECT * FROM user";
return jdbcTemplate.query(sql, new UserRowMapper());
}
(採用的RowMapper
介面實現類仍為上述建立的UserRowMapper
)
(2)在TestJdbc測試類中新增測試方法,如下:
@Test
public void runUserGetAll() {
List<User> userList = userDaoImpl.getAll();
System.out.println(userList);
}
Spring配置c3p0連線池
c3p0連線池介紹
在上述中提及了使用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;
/**
* <p>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.
* <P>
* The {@code DataSource} interface is implemented by a driver vendor.
* There are three types of implementations:
* <OL>
* <LI>Basic implementation -- produces a standard {@code Connection}
* object
* <LI>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.
* <LI>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.
* </OL>
* <P>
* 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.
* <P>
* 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.
* <p>
* An implementation of {@code DataSource} must include a public no-arg
* constructor.
*
* @since 1.4
*/
public interface DataSource extends CommonDataSource, Wrapper {
/**
* <p>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;
/**
* <p>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連線池是如何使用的吧。
使用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,如下:
<?xml version="1.0" encoding="UTF-8"?>
<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">
<!-- 配置c3p0連線池 -->
<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>
<!-- 建立JdbcTemplate物件 -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 建立service和dao物件,在service注入dao物件 -->
<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檔案內容資訊,如下:
<?xml version="1.0" encoding="UTF-8"?>
<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"/>
<!-- 配置c3p0連線池 -->
<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>
<!-- 建立JdbcTemplate物件 -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 建立service和dao物件,在service注入dao物件 -->
<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);
}