1. 程式人生 > >Spring框架自學之路——JdbcTemplate

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獲取,每次呼叫DriverManagerDataSourcegetConnection獲取對資料庫的連線,都相當於建立一個新的連線,這種方式下耗費記憶體和時間,實用性低。我們需要一個數據庫連線池,能有效地負責建立、管理和分配資料庫連線。為了方便對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&trade; 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.jarmchange-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);
}

知識擴充套件或參考