Spring框架自学之路——JdbcTemplate

目录

  • 目录
  • 介绍
  • 使用JdbcTemplate
    • 准备工作
    • 添加操作
    • 更新操作
    • 删除操作
    • 查询操作
      • 返回一个值
      • 返回对象(返回一行数据)
      • 返回List集合(返回多行数据)
  • Spring配置c3p0连接池
    • c3p0连接池介绍
    • 使用c3p0连接池
  • 知识扩展或参考

介绍

  此前入门介绍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。内容如下:


<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 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);
}

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;

/**
 * 

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: *

    *
  1. Basic implementation -- produces a standard {@code Connection} * object *
  2. 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. *
  3. 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连接池是如何使用的吧。

使用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,如下:


<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);
}

知识扩展或参考

  1. JDBC连接数据库的四种方式:DriverManager,DataSource,DBCP,C3P0
  2. 使用动态代理实现自定义连接池
  3. javax.sql.DataSource 和DriverManager有什么区别?
  4. javax.sql.DataSource
  5. spring配置datasource三种方式 数据库连接池
  6. Java DataSource 访问数据库
  7. DBCP连接池与C3P0连接池的比较
  8. c3p0和dbcp的区别

你可能感兴趣的:(JavaEE)