从零开始写javaweb框架笔记8-细节完善与代码优化

  在前面我们搭建了一个客户管理模块的代码框架,在代码中有很大的to do something或者to do,这些就是要逐渐完善的细节,我们将分别完善服务层,控制层,视图层,并且对代码进行优化。

1  完善服务层

  添加SLF4J用于提供日志API,使用Log4J作为实现,配置如下:

<!--SLF4J-->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>1.7.7</version>
        </dependency>
为了让Log4J起作用,必须在main/resources目录下创建一个名为log4j.properties的文件,内容如下:

log4j.rootLogger=ERROR,console,file


log4j.appender.console=org.apache.log4j.ConsoleAppender
log4j.appender.console.layout=org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern=%m%n


log4j.appender.file=org.apache.log4j.DailyRollingFileAppender
log4j.appender.file.File=${user.home}/logs/book.log
log4j.appender.file.DatePattern='_'yyyyMMDD
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=%d{HH:mm:ss,SSS} %p %c (%L) -
%m%n


log4j.logger.org.jack=DEBUG

     上面把日志级别设置为ERROR,并且提供了两种日志appender,分别是console与file。需要对这两种日志分别配置,然后指定只有org.jack包下的类才能输出DEBUG级别的日志。

添加MySQL依赖,用于提供JDBC实现,配置如下:

<!--Mysql-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.33</version>
            <scope>runtime</scope>
        </dependency>

添加两个 Apache Commons  依赖 ,用于提供常用的工具类,配置如下:

<!--Apache Commons Lang-->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.3.2</version>
        </dependency>

        <!--Apache Commons Collections-->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-collections4</artifactId>
            <version>4.0</version>
        </dependency>

打开CustomerService,下面开始实现getCustomerList方法。

  该方法需要获取List,也就是所有的客户信息(此时并没有考虑分页,后续会增加此特性),我们需要执行一条select语句(Statement),获取相应的结果集(ResultSet),然后将其放入一个List中。不过在查询之前,我们必须获取数据库连接(Connection)

   我们可使用JDBC来完成以上的操作,为了让数据库信息可配置,我们在classpath下创建一个config.properties文件,该文件位于src/main/resources目录中(注意:对于Maven目录结构而言,classpath指的就是java与resources这两个根目录。)
config.properties文件的内容如下:

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/demo
jdbc.username=root
jdbc.password=root

属性文件准备好了,那么我们还需要一个类来读取该属性文件,改名编写一个PropsUtil工具类来进行读取config.properties里面的内容,下面是PropsUtil工具类的代码:

package org.jack.com.util;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Created by jack on 2015/12/26.
 * 属性文件工具类
 */
public final class PropsUtil {
    private static final Logger LOGGER = LoggerFactory.getLogger(PropsUtil.class);

    /*
    * 加载属性文件
    *
    * */
    public static Properties loadProps(String fileName) {
        Properties properties = null;
        InputStream inputStream = null;
        try {
            inputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream(fileName);
            if (inputStream == null) {
                throw new FileNotFoundException(fileName + " file is not found!");
            }
            properties = new Properties();
            properties.load(inputStream);
        } catch (IOException e) {
            LOGGER.error("load properties file failure", e);
        } finally {
            if (inputStream != null) {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    LOGGER.error("close input stream failure", e);
                }
            }
        }
        return properties;
    }

    /*
    * 获取字符型属性(默认为空字符串)
    *
    * */
    public static String getString(Properties props, String key) {
        return getString(props, key, "");
    }

    /*
    * 获取字符型属性(可指定默认值)
    * */
    public static String getString(Properties props, String key, String
            defaultValue) {
        String value = defaultValue;
        if (props.containsKey(key)) {
            value = props.getProperty(key);
        }
        return value;
    }

    /*
    * 获取数值类型属性(默认为0)
    * */
    public static int getInt(Properties props, String key) {
        return getInt(props, key, 0);
    }

    /*
    * 获取数值类型属性(可指定默认值)
    * */
    public static int getInt(Properties props, String key, int defaultValue) {
        int value = defaultValue;
        if (props.containsKey(key)) {
            value = CastUtil.castInt(props.getProperty(key));
        }
        return value;
    }

    /*
    * 获取布尔型属性(默认值为false)
    * */
    public static boolean getBoolean(Properties props, String key) {
        return getBoolean(props, key, false);
    }

    /*
    * 获取布尔型属性(可指定默认值)
    * */
    public static boolean getBoolean(Properties props, String key, Boolean defaultValue) {
        boolean value = defaultValue;
        if (props.containsKey(key)) {
            value = CastUtil.castBoolean(props.getProperty(key));
        }
        return value;
    }
}

上面的类中,最关键的是loadProps方法,我们只需传递属性文件的名称,即可返回一个Properties对象,然后再根据getString,getInt,getBoolean这些方法由key获取指定类型的value,同时也可指定defaultValue作为默认值。

在PropsUtil类中,我们用到了CastUtil类,该类是为处理一些数据转型操作而作准备的,代码如下:

<pre name="code" class="java">package org.jack.com.util;

import org.apache.commons.lang3.StringUtils;

/**
 * Created by jack on 2015/12/26.
 * 转型操作工具类
 */
public final class CastUtil {
    /*
    * 转为String型
    * */
    public static String castString(Object obj) {
        return CastUtil.castString(obj, "");
    }

    /*
    * 转为String型(提供默认值)
    * */
    public static String castString(Object obj, String defaultValue) {
        return obj != null ? String.valueOf(obj) : defaultValue;
    }

    /*
    * 转为double型
    * */
    public static double castDouble(Object obj) {
        return castDouble(obj, 0);
    }

    /*
    * 转为double型(提供默认值)
    * */
    public static double castDouble(Object obj, Double defaultValue) {
        double doubleValue = defaultValue;
        if (obj != null) {
            String strValue = castString(obj);
            if (StringUtils.isNotEmpty(strValue)) {
                try {
                    doubleValue = Double.parseDouble(strValue);
                } catch (NumberFormatException e) {
                    defaultValue = defaultValue;
                }

            }
        }
        return doubleValue;
    }

    /*
    * 转为long型
    * */
    public static long castLong(Object obj) {
        return castLong(obj, 0);
    }

    /*
     * 转为long型(提供默认值)
     * */
    public static long castLong(Object obj, long defaultValue) {
        long longValue = defaultValue;
        if (obj != null) {
            String strValue = castString(obj);
            if (StringUtils.isNotEmpty(strValue)) {
                try {
                    longValue = Long.parseLong(strValue);
                }catch (NumberFormatException e){
                    longValue=defaultValue;
                }

            }
        }
        return longValue;
    }

    /*
    * 转为int型
    * */
    public static int castInt(Object obj){
        return castInt(obj,0);
    }
    /*
    * 转为int型(提供默值)
    * */
    public static int castInt(Object obj,int defaultValue){
        int intValue=defaultValue;
        if (obj!=null){
            String strValue=castString(obj);
            if(StringUtils.isNotEmpty(strValue)){
                try {
                    intValue=Integer.parseInt(strValue);
                }catch (NumberFormatException e){
                    intValue=defaultValue;
                }

            }
        }
        return intValue;
    }

    /*
    * 转为boolean型
    * */
    public static boolean castBoolean(Object obj){
        return castBoolean(obj,false);
    }
    /*
    * 转为boolean型(提供默认值)
    * */
    public static boolean castBoolean(Object obj,boolean defaultValue){
        boolean booleanValue=defaultValue;
        if(obj!=null){
            booleanValue=Boolean.parseBoolean(castString(obj));
        }
        return booleanValue;
    }

}

 在CastUtil类中,我们用到了StringUtil类,它用于提供一些字符串操作,代码如下: 

package org.jack.com.util;

import org.apache.commons.lang3.StringUtils;

/**
 * Created by jack on 2015/12/26.
 * 字符串工具类
 */
public final class StringUtil {
    /*
    * 判断字符串是否为空
    * */
    public static boolean isEmpty(String str){
        if(str != null){
            str=str.trim();
        }
        return StringUtils.isEmpty(str);
    }
    /*
    * 判断字符串是否非空
    * */
    public static boolean isNotEmpty(String str){
        return !isEmpty(str);
    }
 }

上面只是对Apache Commons类库做了一个简单的封装。同理,我们可以做一个CollectionUtil,用于提供一些集合操作,代码如下:

package org.jack.com.util;

import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.MapUtils;

import java.util.Collection;
import java.util.Map;

/**
 * Created by jack on 2015/12/26.
 * 集合工具类
 */
public final class CollectionUtil {
    /**
     * 判断Collection是否为空
     */
    public static boolean isEmpty(Collection<?> collection){
        return CollectionUtils.isEmpty(collection);
    }
    /**
     *判断Collection是否非空
     */
    public static boolean isNotEmpty(Collection<?> collection){
        return !isEmpty(collection);
    }
    /**
     * 判断Map是否为空
     */
    public static boolean isEmpty(Map<?,?> map){
        return MapUtils.isEmpty(map);
    }
    /**
     * 判断Map是否非空
     */
    public static boolean isNotEmpty(Map<?,?> map){
        return !isEmpty(map);
    }
}

    上面我们写了四个工具类,每个工具类的分工各不相同,它们在后面还会经常用到,并且会不断完善这些工具类。

现在回到CustomerService,我们需要在该类中执行数据库操作,也就是需要编写一些JDBC的代码,首先使用PropsUtil读取config.properties配置文件,获取与JDBC相关的配置项。

     我们在CustomerService中为这些配置项定义一些常量,并提供一个“静态代码块”,来初始化这些常量,就像下面这样:

private static final String DRIVER;
    private static final String URL;
    private static final String USERNAME;
    private static final String PASSWORD;

    static {
        Properties conf= PropsUtil.loadProps("config.properties");
        DRIVER=conf.getProperty("jdbc:driver");
        URL=conf.getProperty("jdbc.url");
        USERNAME= conf.getProperty("jdbc.username");
        PASSWORD=conf.getProperty("jdbc.password");
    }

以getCustomerList为例,我们可以这样写JDBC代码:

<pre name="code" class="java"><pre name="code" class="java">package org.jack.com.service;

import org.jack.com.model.Customer;
import org.jack.com.util.PropsUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.sql.*;
import java.util.*;

/**
 * Created by jack on 2015/11/29.
 * 提供客户数据服务
 */
public class CustomerService {
    private static final String DRIVER;
    private static final String URL;
    private static final String USERNAME;
    private static final String PASSWORD;
    private static final Logger LOGGER = LoggerFactory.getLogger(CustomerService.class);
    static {
        Properties conf= PropsUtil.loadProps("config.properties");
        DRIVER=conf.getProperty("jdbc.driver");
        URL=conf.getProperty("jdbc.url");
        USERNAME= conf.getProperty("jdbc.username");
        PASSWORD=conf.getProperty("jdbc.password");
        try{
            Class.forName(DRIVER);
        }catch (ClassNotFoundException e){
            LOGGER.error("can not load jdbc driver",e);
        }
    }

    /*
    * 获取客户列表
    * */
    public List<Customer> getCustomerList(String keyword){
        //to do something
        Connection conn=null;
        try{
            List<Customer> customerList=new ArrayList<Customer>();
            String sql="select * from customer";
            conn= DriverManager.getConnection(URL,USERNAME,PASSWORD);
            PreparedStatement pstmt=conn.prepareStatement(sql);
            ResultSet rs=pstmt.executeQuery();
            while(rs.next()){
                Customer customer=new Customer();
                customer.setId(rs.getLong("id"));
                customer.setName(rs.getString("name"));
                customer.setContact(rs.getString("contact"));
                customer.setTelephone(rs.getString("telephone"));
                customer.setEmail(rs.getString("email"));
                customer.setRemark(rs.getString("remark"));
                customerList.add(customer);
            }
            return customerList;
        }catch (SQLException e){
            LOGGER.error("execute sql failure",e);
        }finally {
            if (conn !=null){
                try {
                    conn.close();
                }catch (SQLException e){
                    LOGGER.error("close connection failure",e);
                }
            }
        }
        return  null;
    }

    /*
    * 获取客户
    * */
    public  Customer getCustomer(long id){
        //to do something
        return null;
    }

    /*
    * 创建客户
    * */
    public boolean createCustomer(Map<String,Object> fieldMap){
        //to do something
        return false;
    }

    /*
    * 更新客户
    * */
    public boolean updateCustomer(long id,Map<String,Object> fieldMap){
        //to do something
        return false;
    }

    /*
    * 删除客户
    * */
    public boolean deleteCustomer(long id){
        //to do something
        return false;
    }

   }


 
   选中getCustomerList方法右击,选择run进行单元测试,如果没有错误的话,应该是“绿条”,表示可以测试通过,相反,如果出现“红条”就表示测试失败,我们可以查看控制台以了解具体的错误原因。正确的测试结果如下图: 



  虽然以上的代码可以运行,基本的功能是可以实现的,但是问题还很多,具体包括以下2个方面:

1)在CustomerService类中读取config.properties文件,这是不合理的,毕竟将来还有很多其他Service类需要做同样的事情,我们最好能将这些公共性的代码提起出来。

2)执行一条select语句需要编写一大堆代码,而且必须使用try........catch.....finally结构,开发效率明显不高。

    那么问题来了,我们该如何解决这两个问题?首先我们来解决第一个问题。

创建一个org.jack.com.helper包,在该包中创建一个DatabaseHelper类,代码如下:

package org.jack.com.helper;

import org.jack.com.util.PropsUtil;
import org.slf4j.LoggerFactory;
import org.slf4j.Logger;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Properties;

/**
 * Created by jack on 2015/12/26.
 * 数据库操作助手类
 */
public final class DatabaseHelper {
    private static final Logger LOGGER= LoggerFactory.getLogger(DatabaseHelper.class);
    private static final String DRIVER;
    private static final String URL;
    private static final String USERNAME;
    private static final String PASSWORD;

    static {
        Properties conf= PropsUtil.loadProps("config.properties");
        DRIVER=conf.getProperty("jdbc.driver");
        URL=conf.getProperty("jdbc.url");
        USERNAME=conf.getProperty("jdbc.username");
        PASSWORD=conf.getProperty("jdbc.password");
        try{
            Class.forName(DRIVER);
        }catch (ClassNotFoundException e){
            LOGGER.error("can not load jdbc driver",e);
        }
    }
    /**
     * 获取数据库连接
     */
    public static Connection getConnection(){
        Connection conn=null;
        try {
            conn= DriverManager.getConnection(URL,USERNAME,PASSWORD);
        }catch (SQLException e){
            LOGGER.error("get connection failure",e);
        }
        return conn;
    }

    /**
     * 关闭数据库连接
     */
    public static void closeConnection(Connection conn){
        if(conn != null){
            try {
                conn.close();
            }catch (SQLException e){
                LOGGER.error("close connection failure",e);
            }
        }
    }

}
     在DatabaseHelper中包含一些静态方法,用它们来封装数据库的相关操作,目前提供了“获取数据库连接”与“关闭数据库连接”两个助手方法。

      现在CustomerService可以稍微的简化下了:

package org.jack.com.service;

import org.jack.com.helper.DatabaseHelper;
import org.jack.com.model.Customer;
import org.jack.com.util.PropsUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.sql.*;
import java.util.*;

/**
 * Created by jack on 2015/11/29.
 * 提供客户数据服务
 */
public class CustomerService {
    /*private static final String DRIVER;
    private static final String URL;
    private static final String USERNAME;
    private static final String PASSWORD;*/
    private static final Logger LOGGER = LoggerFactory.getLogger(CustomerService.class);
    /*static {
        Properties conf= PropsUtil.loadProps("config.properties");
        DRIVER=conf.getProperty("jdbc.driver");
        URL=conf.getProperty("jdbc.url");
        USERNAME= conf.getProperty("jdbc.username");
        PASSWORD=conf.getProperty("jdbc.password");
        try{
            Class.forName(DRIVER);
        }catch (ClassNotFoundException e){
            LOGGER.error("can not load jdbc driver",e);
        }
    }*/

    /*
    * 获取客户列表
    * */
    public List<Customer> getCustomerList(String keyword){
        //to do something
        Connection conn=null;
        try{
            List<Customer> customerList=new ArrayList<Customer>();
            String sql="select * from customer";
            conn= DatabaseHelper.getConnection();//1
            PreparedStatement pstmt=conn.prepareStatement(sql);
            ResultSet rs=pstmt.executeQuery();
            while(rs.next()){
                Customer customer=new Customer();
                customer.setId(rs.getLong("id"));
                customer.setName(rs.getString("name"));
                customer.setContact(rs.getString("contact"));
                customer.setTelephone(rs.getString("telephone"));
                customer.setEmail(rs.getString("email"));
                customer.setRemark(rs.getString("remark"));
                customerList.add(customer);
            }
            return customerList;
        }catch (SQLException e){
            LOGGER.error("execute sql failure",e);
        }finally {
            /*if (conn !=null){
                try {
                    conn.close();
                }catch (SQLException e){
                    LOGGER.error("close connection failure",e);
                }
            }*/
            DatabaseHelper.closeConnection(conn);//2
        }
        return null;
    }

    /*
    * 获取客户
    * */
    public  Customer getCustomer(long id){
        //to do something
        return null;
    }

    /*
    * 创建客户
    * */
    public boolean createCustomer(Map<String,Object> fieldMap){
        //to do something
        return false;
    }

    /*
    * 更新客户
    * */
    public boolean updateCustomer(long id,Map<String,Object> fieldMap){
        //to do something
        return false;
    }

    /*
    * 删除客户
    * */
    public boolean deleteCustomer(long id){
        //to do something
        return false;
    }

   }

  注意以上代码1,2两处的修改,的确简化了一些,至少每个Service类不会出现那些静态变量与代码块了,公共的代码放到了DatabaseHelper中从而得到了重用。

     下面我们来解决第二个问题,实际上就是如何使代码变得更加精简。

      著名的Apache Common项目中有一款名为DbUtils的类库,为我们提供了一个JDBC的封装,下面我们就用这款工具来解决该问题。

      首先,需要在pom.xml中添加一个DbUtils的依赖:

<!--Apache Commons DbUtils-->
        <dependency>
            <groupId>commons-dbutils</groupId>
            <artifactId>commons-dbutils</artifactId>
            <version>1.6</version>
        </dependency>

然后,在DatabaseHelper中添加如下代码:

package org.jack.com.helper;

import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import org.jack.com.util.PropsUtil;
import org.slf4j.LoggerFactory;
import org.slf4j.Logger;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.List;
import java.util.Properties;

/**
 * Created by jack on 2015/12/26.
 * 数据库操作助手类
 */
public final class DatabaseHelper {
    private static final Logger LOGGER= LoggerFactory.getLogger(DatabaseHelper.class);
    private static final String DRIVER;
    private static final String URL;
    private static final String USERNAME;
    private static final String PASSWORD;

    static {
        Properties conf= PropsUtil.loadProps("config.properties");
        DRIVER=conf.getProperty("jdbc.driver");
        URL=conf.getProperty("jdbc.url");
        USERNAME=conf.getProperty("jdbc.username");
        PASSWORD=conf.getProperty("jdbc.password");
        try{
            Class.forName(DRIVER);
        }catch (ClassNotFoundException e){
            LOGGER.error("can not load jdbc driver",e);
        }
    }

    private static final QueryRunner QUERY_RUNNER=new QueryRunner();

    /**
     * 获取数据库连接
     */
    public static Connection getConnection(){
        Connection conn=null;
        try {
            conn= DriverManager.getConnection(URL,USERNAME,PASSWORD);
        }catch (SQLException e){
            LOGGER.error("get connection failure",e);
        }
        return conn;
    }

    /**
     * 关闭数据库连接
     */
    public static void closeConnection(Connection conn){
        if(conn != null){
            try {
                conn.close();
            }catch (SQLException e){
                LOGGER.error("close connection failure",e);
            }
        }
    }

    /**
     * 查询实体列表
     */
    public static <T> List<T> queryEntityList(Class<T> entityClass,String sql,Object... params){
        List<T> entityList;
        Connection conn=getConnection();
        try{
            entityList=QUERY_RUNNER.query(conn,sql,new BeanListHandler<T>(entityClass),params);
        }catch (SQLException e){
            LOGGER.error("query entity list failure",e);
            throw new RuntimeException(e);
        }finally {
            closeConnection(conn);
        }
        return entityList;
    }

}
   注意,上面的queryEntityList方法还有问题,在下面进行改进。使用DbUtils提供的QueryRunner对象可以面向实体(Entity)进行查询。

实际上,DbUtils首先执行SQL语句并返回一个ResultSet,随后通过反射去创建并初始化实体对象。由于我们需要返的是List,因此可以使用BeanListHandler。

     最后,改写CustomerService的getCustomerList方法:

 

 /*
    * 获取客户列表
    * */
    public List<Customer> getCustomerList(){
        //to do something
        Connection conn=DatabaseHelper.getConnection();;
        try{

            String sql="select * from customer";

            return DatabaseHelper.queryEntityList(Customer.class,conn,sql);
        }catch (SQLException e){
            LOGGER.error("execute sql failure",e);
        }finally {

            DatabaseHelper.closeConnection(conn);//2
        }
        return null;
    }

   上面的代码,有问题,下面会进行改进。

    现在的代码简单了很多,我们不再面对PrepareStatement与ResultSet了,只需要使用DatabaseHelper就能执行数据库操作。我们每次都需要创建一个Connection,然后进行数据库操作,最后关闭Connection。

     如何能让Connection对开发人员透明?也就是说,如何隐藏掉创建与关闭Connection的代码呢?

    为了确保一个线程只有一个Connection,我们可以使用ThreadLocal来存放本地线程变量。也就是说,将当前线程中的Connection放入ThreadLocal中存起来,这些Connection一定不会出现不安全问题,可以将ThreadLocal理解为一个隔离线程的容器。

     现在对DatabaseHelper进行调整,具体代码如下:

package org.jack.com.helper;

import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import org.jack.com.util.PropsUtil;
import org.slf4j.LoggerFactory;
import org.slf4j.Logger;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.List;
import java.util.Properties;

/**
 * Created by jack on 2015/12/26.
 * 数据库操作助手类
 */
public final class DatabaseHelper {
    private static final Logger LOGGER = LoggerFactory.getLogger(DatabaseHelper.class);
    private static final String DRIVER;
    private static final String URL;
    private static final String USERNAME;
    private static final String PASSWORD;

    static {
        Properties conf = PropsUtil.loadProps("config.properties");
        DRIVER = conf.getProperty("jdbc.driver");
        URL = conf.getProperty("jdbc.url");
        USERNAME = conf.getProperty("jdbc.username");
        PASSWORD = conf.getProperty("jdbc.password");
        try {
            Class.forName(DRIVER);
        } catch (ClassNotFoundException e) {
            LOGGER.error("can not load jdbc driver", e);
        }
    }

    private static final QueryRunner QUERY_RUNNER = new QueryRunner();
    private static final ThreadLocal<Connection> CONNECTION_HOLDER = new ThreadLocal<Connection>();

    /**
     * 获取数据库连接
     */
    public static Connection getConnection() {
        Connection conn = CONNECTION_HOLDER.get();//1
        if (conn == null) {
            try {
                conn = DriverManager.getConnection(URL, USERNAME, PASSWORD);
            } catch (SQLException e) {
                LOGGER.error("get connection failure", e);
            } finally {
                CONNECTION_HOLDER.set(conn);
            }
        }
        return conn;
    }

    /**
     * 关闭数据库连接
     */
    public static void closeConnection() {
        Connection conn=CONNECTION_HOLDER.get();//1
        if (conn != null) {
            try {
                conn.close();
            } catch (SQLException e) {
                LOGGER.error("close connection failure", e);
                throw new RuntimeException(e);
            }finally {
                CONNECTION_HOLDER.remove();//3
            }
        }
    }

    /**
     * 查询实体列表
     */
    public static <T> List<T> queryEntityList(Class<T> entityClass, String sql, Object... params) {
        List<T> entityList;

        try {
            Connection conn = getConnection();
            entityList = QUERY_RUNNER.query(conn, sql, new BeanListHandler<T>(entityClass), params);
        } catch (SQLException e) {
            LOGGER.error("query entity list failure", e);
            throw new RuntimeException(e);
        } finally {
            closeConnection();
        }
        return entityList;
    }

}

 每次获取Connection时,首先在ThreadLocal中寻找(见1),若不存在,则创建一个新的Connection,并将其放入ThreadLocal中(见2)。当Connection使用完毕后,需要移除ThreadLocal中持有的Connection(见3)
    现在CustomerService的getCustomerList方法看起来更加简单了:
package org.jack.com.service;

import org.jack.com.helper.DatabaseHelper;
import org.jack.com.model.Customer;
import org.jack.com.util.PropsUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.sql.*;
import java.util.*;

/**
 * Created by jack on 2015/11/29.
 * 提供客户数据服务
 */
public class CustomerService {
    private static final Logger LOGGER = LoggerFactory.getLogger(CustomerService.class);

    /*
    * 获取客户列表
    * */
    public List<Customer> getCustomerList(){
        //to do something
        String sql="select * from customer";
        return DatabaseHelper.queryEntityList(Customer.class,sql);

    }

    /*
    * 获取客户
    * */
    public  Customer getCustomer(long id){
        //to do something
        return null;
    }

    /*
    * 创建客户
    * */
    public boolean createCustomer(Map<String,Object> fieldMap){
        //to do something
        return false;
    }

    /*
    * 更新客户
    * */
    public boolean updateCustomer(long id,Map<String,Object> fieldMap){
        //to do something
        return false;
    }

    /*
    * 删除客户
    * */
    public boolean deleteCustomer(long id){
        //to do something
        return false;
    }

   }

   使用同样的技巧,我们可以在DatabaseHelper中添加一个queryEntity方法,用户查询单个实体对象。

 /**
     * 查询实体
     */
    public static <T> T queryEntity(Class<T> entityClass,String sql,Object... params){
        T entity;
        try {
            Connection conn=getConnection();
            entity=QUERY_RUNNER.query(conn,sql,new BeanHandler<T>(entityClass),params);
        }catch (SQLException e){
            LOGGER.error("query entity failure",e);
            throw new RuntimeException(e);
        }finally {
            closeConnection();
        }
        return entity;
    }


需要注意的是,此时我们使用的BeanHandler,而不是BeanListHandler。实际上,DbUtils为我们提供了很多类似的Handler,包括:

BeanHandler-------------------------------返回Bean

BeanListHandler---------------------------返回List对象

BeanMapHandler--------------------------返回Map对象

ArrayHandler-----------------------------返回Object[]对象

ArrayListHandler---------------------------返回List对象

MapHandler--------------------------------返回Map对象

MapListHandler---------------------------返回List对象

ScalarHandler----------------------------返回某列的值

ColumnListHandler--------------------------返回某列的值列表

KeyedHandler-----------------------------------返回Map对象,需指定列名。

   以上这些Handler都实现了ResultSetHandler。

    查询并不一定是基于单表的,将来有可能连接多个表进行查询,因此我们有必要提供一个更为强大的查询方法,输入一个SQL与动态参数,输入一个List对象,其中的Map表示列名与列值得映射关系。

    我们可以借助MapListHandler轻松实现:

   /**
     * 执行查询
     */
    public static List<Map<String,Object>> executeQuery(String sql,Object...params){
        List<Map<String,Object>> result;
        try {
            Connection conn=getConnection();
            result=QUERY_RUNNER.query(conn,sql,new MapListHandler(),params)
        }catch (Exception e){
            LOGGER.error("execute query failure",e);
            throw new RuntimeException(e);
        }
        return result;
    }

   除了查询语句以外,还包括几种更新语句,例如update,insert,delete等,我们再提供一个通用的执行更新语句的方法。

/**
     * 执行更新语句(包括update,inert,delete)
     */
    public static int executeUpdate(String sql,Object... params){
        int rows=0;
        try {
            Connection conn=getConnection();
            rows=QUERY_RUNNER.update(conn,sql,params);
        }catch (SQLException e){
            LOGGER.error("execute update failure",e);
            throw new RuntimeException(e);
        }finally {
            closeConnection();
        }
        return rows;
    }
   该方法返回执行后受影响的行数,也就是说,更新了多少条记录。根据这个通用的方法,我们可以分别提供三种具体的数据库更新操作:

package org.jack.com.helper;

import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import org.apache.commons.dbutils.handlers.MapListHandler;
import org.jack.com.util.CollectionUtil;
import org.jack.com.util.PropsUtil;
import org.slf4j.LoggerFactory;
import org.slf4j.Logger;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Properties;

/**
 * Created by jack on 2015/12/26.
 * 数据库操作助手类
 */
public final class DatabaseHelper {
    private static final Logger LOGGER = LoggerFactory.getLogger(DatabaseHelper.class);
    private static final String DRIVER;
    private static final String URL;
    private static final String USERNAME;
    private static final String PASSWORD;

    static {
        Properties conf = PropsUtil.loadProps("config.properties");
        DRIVER = conf.getProperty("jdbc.driver");
        URL = conf.getProperty("jdbc.url");
        USERNAME = conf.getProperty("jdbc.username");
        PASSWORD = conf.getProperty("jdbc.password");
        try {
            Class.forName(DRIVER);
        } catch (ClassNotFoundException e) {
            LOGGER.error("can not load jdbc driver", e);
        }
    }

    private static final QueryRunner QUERY_RUNNER = new QueryRunner();
    private static final ThreadLocal<Connection> CONNECTION_HOLDER = new ThreadLocal<Connection>();

    /**
     * 获取数据库连接
     */
    public static Connection getConnection() {
        Connection conn = CONNECTION_HOLDER.get();//1
        if (conn == null) {
            try {
                conn = DriverManager.getConnection(URL, USERNAME, PASSWORD);
            } catch (SQLException e) {
                LOGGER.error("get connection failure", e);
            } finally {
                CONNECTION_HOLDER.set(conn);
            }
        }
        return conn;
    }

    /**
     * 关闭数据库连接
     */
    public static void closeConnection() {
        Connection conn=CONNECTION_HOLDER.get();//1
        if (conn != null) {
            try {
                conn.close();
            } catch (SQLException e) {
                LOGGER.error("close connection failure", e);
                throw new RuntimeException(e);
            }finally {
                CONNECTION_HOLDER.remove();
            }
        }
    }

    /**
     * 查询实体列表
     */
    public static <T> List<T> queryEntityList(Class<T> entityClass, String sql, Object... params) {
        List<T> entityList;

        try {
            Connection conn = getConnection();
            entityList = QUERY_RUNNER.query(conn, sql, new BeanListHandler<T>(entityClass), params);
        } catch (SQLException e) {
            LOGGER.error("query entity list failure", e);
            throw new RuntimeException(e);
        } finally {
            closeConnection();
        }
        return entityList;
    }

    /**
     * 查询实体
     */
    public static <T> T queryEntity(Class<T> entityClass,String sql,Object... params){
        T entity;
        try {
            Connection conn=getConnection();
            entity=QUERY_RUNNER.query(conn,sql,new BeanHandler<T>(entityClass),params);
        }catch (SQLException e){
            LOGGER.error("query entity failure",e);
            throw new RuntimeException(e);
        }finally {
            closeConnection();
        }
        return entity;
    }

    /**
     * 执行查询
     */
    public static List<Map<String,Object>> executeQuery(String sql,Object...params){
        List<Map<String,Object>> result;
        try {
            Connection conn=getConnection();
            result=QUERY_RUNNER.query(conn,sql,new MapListHandler(),params);
        }catch (Exception e){
            LOGGER.error("execute query failure",e);
            throw new RuntimeException(e);
        }
        return result;
    }

    /**
     * 执行更新语句(包括update,inert,delete)
     */
    public static int executeUpdate(String sql,Object... params){
        int rows=0;
        try {
            Connection conn=getConnection();
            rows=QUERY_RUNNER.update(conn,sql,params);
        }catch (SQLException e){
            LOGGER.error("execute update failure",e);
            throw new RuntimeException(e);
        }finally {
            closeConnection();
        }
        return rows;
    }
    /**
     * 插入实体
     */
    public static <T> boolean insertEntity(Class<T> entityClass,Map<String,Object> fieldMap){
        if(CollectionUtil.isEmpty(fieldMap)){
            LOGGER.error("can not insert entity:fieldMap is empty");
            return false;
        }
        String sql="INSERT INTO "+getTableName(entityClass);
        StringBuilder columns=new StringBuilder("(");
        StringBuilder values=new StringBuilder("(");
        for (String fieldName : fieldMap.keySet()){
            columns.append(fieldName).append(", ");
            values.append("?, ");
        }
        columns.replace(columns.lastIndexOf(", "),columns.length(),") ");
        values.replace(values.lastIndexOf(", "),values.length(),") ");
        sql +=columns + " VALUES " + values;

        Object[] params=fieldMap.values().toArray();
        return executeUpdate(sql,params) == 1;
    }

    /**
     * 更新实体
     * @param entityClass
     * @return boolean
     */
    public static <T> boolean updateEntity(Class<T> entityClass,long id,Map<String,Object> fieldMap){
        if (CollectionUtil.isEmpty(fieldMap)){
            LOGGER.error("can not update entity:fieldMap is empty");
            return false;
        }

        String sql="UPDATE " + getTableName(entityClass) + " SET ";
        StringBuilder columns=new StringBuilder();
        for (String fieldName : fieldMap.keySet()){
            columns.append(fieldName).append("=?, ");
        }
        sql += columns.substring(0,columns.lastIndexOf(", ")) +"WHERE id =?";
        List<Object> paramList =new ArrayList<Object>();
        paramList.addAll(fieldMap.values());
        paramList.add(id);
        Object[] params=paramList.toArray();
        return executeUpdate(sql,params) == 1;
    }

    /**
     * 删除实体
     * @param entityClass
     * @return
     */
    public static <T> boolean deleteEntity(Class<T> entityClass,long id){
        String sql="DELETE FROM "+getTableName(entityClass)+" WHERE id=?";
        return executeUpdate(sql,id) == 1;
    }

    private static String getTableName(Class<?> entityClass){
        return entityClass.getSimpleName();//返回类名
    }

}

  有了insertEntity,updateEntity,deleteEntity后,我们可以快速完成CustomerService中剩下的几个方法:

package org.jack.com.service;

import org.jack.com.helper.DatabaseHelper;
import org.jack.com.model.Customer;
import org.jack.com.util.PropsUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.sql.*;
import java.util.*;

/**
 * Created by jack on 2015/11/29.
 * 提供客户数据服务
 */
public class CustomerService {
    private static final Logger LOGGER = LoggerFactory.getLogger(CustomerService.class);

    /*
    * 获取客户列表
    * */
    public List<Customer> getCustomerList(){
        //to do something
        String sql="select * from customer";
        return DatabaseHelper.queryEntityList(Customer.class,sql);

    }

    /*
    * 获取客户
    * */
    public  Customer getCustomer(long id){
        //to do something
        String sql = "SELECT * FROM customer WHERE id = ?";
        return DatabaseHelper.queryEntity(Customer.class, sql, id);
    }

    /*
    * 创建客户
    * */
    public boolean createCustomer(Map<String,Object> fieldMap){
        //to do something

        return DatabaseHelper.insertEntity(Customer.class,fieldMap);
    }

    /*
    * 更新客户
    * */
    public boolean updateCustomer(long id,Map<String,Object> fieldMap){
        //to do something
        return DatabaseHelper.updateEntity(Customer.class,id,fieldMap);
    }

    /*
    * 删除客户
    * */
    public boolean deleteCustomer(long id){
        //to do something
        return DatabaseHelper.deleteEntity(Customer.class,id);
    }

   }

   现在CustomerService的实现相当精简了,只需要一两行代码即可完成表单的“增删改查”操作。像上面这样,每次需要数据库连接时,就调用getConnection方法,在数据库操作完毕以后,还需要调用closeConnection方法关闭数据库连接。虽然关闭数据库这件事情已经被DatabaseHelper类给封装了,当考虑到如果频繁调用getConnection方式就会频繁创建数据库连接,这样做一定会造成大量的系统开销,毕竟数据库的连接数是有限的。因此,需要考虑一种解决方案,将这些数据库连接进行“池话”,也就是说我们需要弄一个“数据库连接池”出来。Apache DBCP是最好的数据库连接池之一,下面我们就使用这个数据库连接池。

    首先,修改pom.xml文件,添加如下的Maven依赖:

<!--Apache DBCP数据库连接池依赖-->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-dbcp2</artifactId>
            <version>2.0.1</version>
        </dependency>
  然后修改DatabaseHelper的相关代码
package org.jack.com.helper;

import org.apache.commons.dbcp2.BasicDataSource;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import org.apache.commons.dbutils.handlers.MapListHandler;
import org.jack.com.util.CollectionUtil;
import org.jack.com.util.PropsUtil;
import org.slf4j.LoggerFactory;
import org.slf4j.Logger;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Properties;

/**
 * Created by jack on 2015/12/26.
 * 数据库操作助手类
 */
public final class DatabaseHelper {
    private static final Logger LOGGER = LoggerFactory.getLogger(DatabaseHelper.class);

    //查询
    private static final QueryRunner QUERY_RUNNER;
    //保证一个线程一个Connection,线程安全
    private static final ThreadLocal<Connection> CONNECTION_HOLDER ;
    //线程池
    private static final BasicDataSource DATA_SOURCE;

    static {
        CONNECTION_HOLDER = new ThreadLocal<Connection>();
        QUERY_RUNNER = new QueryRunner();

        Properties conf = PropsUtil.loadProps("config.properties");
        String driver = conf.getProperty("jdbc.driver");
        String url = conf.getProperty("jdbc.url");
        String username = conf.getProperty("jdbc.username");
        String passwrod = conf.getProperty("jdbc.password");

        DATA_SOURCE=new BasicDataSource();
        DATA_SOURCE.setDriverClassName(driver);
        DATA_SOURCE.setUrl(url);
        DATA_SOURCE.setUsername(username);
        DATA_SOURCE.setPassword(passwrod);
    }

    /**
     * 获取数据库连接
     */
    public static Connection getConnection() {
        Connection conn = CONNECTION_HOLDER.get();//1
        if (conn == null) {
            try {
                conn = DATA_SOURCE.getConnection();
            } catch (SQLException e) {
                LOGGER.error("get connection failure", e);
                throw new RuntimeException(e);
            } finally {
                CONNECTION_HOLDER.set(conn);
            }
        }
        return conn;
    }



    /**
     * 查询实体列表
     */
    public static <T> List<T> queryEntityList(Class<T> entityClass, String sql, Object... params) {
        List<T> entityList;

        try {
            Connection conn = getConnection();
            entityList = QUERY_RUNNER.query(conn, sql, new BeanListHandler<T>(entityClass), params);
        } catch (SQLException e) {
            LOGGER.error("query entity list failure", e);
            throw new RuntimeException(e);
        }
        return entityList;
    }

    /**
     * 查询实体
     */
    public static <T> T queryEntity(Class<T> entityClass,String sql,Object... params){
        T entity;
        try {
            Connection conn=getConnection();
            entity=QUERY_RUNNER.query(conn,sql,new BeanHandler<T>(entityClass),params);
        }catch (SQLException e){
            LOGGER.error("query entity failure",e);
            throw new RuntimeException(e);
        }
        return entity;
    }

    /**
     * 执行查询
     */
    public static List<Map<String,Object>> executeQuery(String sql,Object...params){
        List<Map<String,Object>> result;
        try {
            Connection conn=getConnection();
            result=QUERY_RUNNER.query(conn,sql,new MapListHandler(),params);
        }catch (Exception e){
            LOGGER.error("execute query failure",e);
            throw new RuntimeException(e);
        }
        return result;
    }

    /**
     * 执行更新语句(包括update,inert,delete)
     */
    public static int executeUpdate(String sql,Object... params){
        int rows=0;
        try {
            Connection conn=getConnection();
            rows=QUERY_RUNNER.update(conn,sql,params);
        }catch (SQLException e){
            LOGGER.error("execute update failure",e);
            throw new RuntimeException(e);
        }
        return rows;
    }
    /**
     * 插入实体
     */
    public static <T> boolean insertEntity(Class<T> entityClass,Map<String,Object> fieldMap){
        if(CollectionUtil.isEmpty(fieldMap)){
            LOGGER.error("can not insert entity:fieldMap is empty");
            return false;
        }
        String sql="INSERT INTO "+getTableName(entityClass);
        StringBuilder columns=new StringBuilder("(");
        StringBuilder values=new StringBuilder("(");
        for (String fieldName : fieldMap.keySet()){
            columns.append(fieldName).append(", ");
            values.append("?, ");
        }
        columns.replace(columns.lastIndexOf(", "),columns.length(),") ");
        values.replace(values.lastIndexOf(", "),values.length(),") ");
        sql +=columns + " VALUES " + values;

        Object[] params=fieldMap.values().toArray();
        return executeUpdate(sql,params) == 1;
    }

    /**
     * 更新实体
     * @param entityClass
     * @return boolean
     */
    public static <T> boolean updateEntity(Class<T> entityClass,long id,Map<String,Object> fieldMap){
        if (CollectionUtil.isEmpty(fieldMap)){
            LOGGER.error("can not update entity:fieldMap is empty");
            return false;
        }

        String sql="UPDATE " + getTableName(entityClass) + " SET ";
        StringBuilder columns=new StringBuilder();
        for (String fieldName : fieldMap.keySet()){
            columns.append(fieldName).append("=?, ");
        }
        sql += columns.substring(0,columns.lastIndexOf(", ")) +"WHERE id =?";
        List<Object> paramList =new ArrayList<Object>();
        paramList.addAll(fieldMap.values());
        paramList.add(id);
        Object[] params=paramList.toArray();
        return executeUpdate(sql,params) == 1;
    }

    /**
     * 删除实体
     * @param entityClass
     * @return
     */
    public static <T> boolean deleteEntity(Class<T> entityClass,long id){
        String sql="DELETE FROM "+getTableName(entityClass)+" WHERE id=?";
        return executeUpdate(sql,id) == 1;
    }

    private static String getTableName(Class<?> entityClass){
        return entityClass.getSimpleName();//返回类名
    }

}

   我们使用Apache DBCP的org.apache.commons.dbcp2.BasicDataSource来获取数据库连接,只需要保证该对象是静态的就行了,通过设置driver,url,username,password来初始化BasicDataSource,并调用其getConnection方法即可获取数据库连接。

     最后,删除以前的closeConnection方法,同时去掉所有closeConnection的finally代码块。

      我们可以运行CustomerServiceTest单元测试类的相关测试方法来验证以上代码的正确性。现在的单元测试存在一个非常严重的问题,比如执行完deleteCustomerTest方法,就不能继续执行getCustomerTest方法了,原因很简单,在测试之前数据库的数据无法自动还原为初始状态。

      JUnit在调用每个@Test方法前,都会调用@Before方法,也就是我们在单元测试类里定义的init,目前在该方法中预留一个to do something,需要在这里初始化数据库,为我们准备一个便于测试的数据库环境。

      此外,为了测试数据库与开发数据库分离,也就是,应该是两个数据库,只是表结构相同而已。我们需要为单元测试单独创建一个数据库,不妨命名为demo_test,需要将demo数据库的customer表复制到demo_test数据库中。

     在Navicat premium中创建demo_test数据库,具体的创建过程前面的笔记有讲解过。然后我们在进行如下具体操作:

1)进入demo数据库,选中customer表,使用ctr+c复制表。

2)进入demo_test数据库,使用ctrl+v键粘贴表,即可复制数据表。

3)在demo_test数据库中,选中customer表,单击右键,单击Truncate Table(或者清空表)菜单项,即可清空表中所有的数据(id将从1开始自增)

    随后,我们需要准备一个customer_init.sql文件,用于存放所有的insert语句,该文件位于test/resources/sql目录下,具体内容如下:

<pre style="background-color:#2b2b2b;color:#a9b7c6;font-family:'Consolas';font-size:16pt;">TRUNCATE customer<span style="color:#cc7832;">;
</span><span style="color:#cc7832;"><strong>INSERT INTO </strong></span>customer (<span style="color:#cc7832;"><strong>name </strong></span><span style="color:#cc7832;">,</span>contact<span style="color:#cc7832;">,</span>telephone<span style="color:#cc7832;">,</span>email<span style="color:#cc7832;">,</span>remark) <span style="color:#cc7832;"><strong>VALUES </strong></span>(<span style="color:#a5c261;"><strong>'customer1'</strong></span><span style="color:#cc7832;">,</span><span style="color:#a5c261;"><strong>'jack'</strong></span><span style="color:#cc7832;">,</span><span style="color:#a5c261;"><strong>'13512345678'</strong></span><span style="color:#cc7832;">,</span><span style="color:#a5c261;"><strong>'jack@gmail.com'</strong></span><span style="color:#cc7832;">,</span><span style="color:#cc7832;"><strong>null</strong></span>)<span style="color:#cc7832;">;
</span><span style="color:#cc7832;"><strong>INSERT INTO </strong></span>customer (<span style="color:#cc7832;"><strong>name </strong></span><span style="color:#cc7832;">,</span>contact<span style="color:#cc7832;">,</span>telephone<span style="color:#cc7832;">,</span>email<span style="color:#cc7832;">,</span>remark) <span style="color:#cc7832;"><strong>VALUES </strong></span>(<span style="color:#a5c261;"><strong>'customer2'</strong></span><span style="color:#cc7832;">,</span><span style="color:#a5c261;"><strong>'rose'</strong></span><span style="color:#cc7832;">,</span><span style="color:#a5c261;"><strong>'13623456789'</strong></span><span style="color:#cc7832;">,</span><span style="color:#a5c261;"><strong>'rose@gmail.com'</strong></span><span style="color:#cc7832;">,</span><span style="color:#cc7832;"><strong>null</strong></span>)<span style="color:#cc7832;">;</span>

     注意,上面的插入语句要全部在同一行,我们需要先执行TRUNCATE语句,清空左右数据,然后再执行INSERT语句,插入相关数据。这里并不需要提供id列,因为该列是自增的。 

   注意:默认情况下IDEA不会再test目录下自动创建resources目录,也就是我们需要手动创建,可使用Alt+Insert快捷键来完成。创建resources目录完毕后,需要右键单击该目录,单击Mark Directory As/Resources Root,将该目录设置为Maven的测试资源目录。需要注意的是,main/java, main/resources, test/java, test/resources这四个目录都是classpath的根目录,当运行单元测试时,遵循“就近原则”,即优先从test/java,test/resources加载类或读取文件。

    现在test目录结构如下图:




    既然已经为单元测试准备了独立的测试数据库,那么就有必要在test/resources目录下提供一个config.properties文件,用于提供测试数据库的相关配置:

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/demo_test
jdbc.username=root
jdbc.password=root
    这里使用的是demo_test数据库,而不是demo数据库。

下面我们利用DatabaseHelper的executeUpdate方法依次执行customer_init.sql中提供的SQL语句。

      下面我们来完成CustomerServiceTest的init方法:

   @Before
    public void init() throws  Exception{
        //to do 初始化数据库
        String file="sql/customer_init.sql";
        InputStream is=Thread.currentThread().getContextClassLoader().getResourceAsStream(file);
        BufferedReader reader=new BufferedReader(new InputStreamReader(is));
        String sql;
        while ((sql=reader.readLine())!=null){
            DatabaseHelper.executeUpdate(sql);
        }
    }

       从当前线程中获取线程上下文中的ClassLoader,通过classpath下的sql/customer_init.sql获取一个InputStream对象,通过该输入流来创建BufferedReader对象,循环读取其中的每一行,并调用DatabaseHelper的executeUpdate方法来执行每条SQL语句。

       如果没有错误,测试CustomerServiceTest出现绿条结果如下:


  但是没必要再所有单元测试类的init方法中都使用类似上面的代码init方法里面的代码,因此有必要将这些代码做一个封装。不妨在DatabaseHelper类中提供一个executeSqlFile方法,只需要提供filePath即可,如下:

/**
     * 执行sql文件
     */
    public static void executeSqlFile(String filePath){
        InputStream is=Thread.currentThread().getContextClassLoader().getResourceAsStream(filePath);
        BufferedReader reader=new BufferedReader(new InputStreamReader(is));
        try {
            String sql;
            while((sql=reader.readLine())!=null){
                executeUpdate(sql);
            }
        }catch (Exception e){
            LOGGER.error("execute sql file failure",e);
            throw new RuntimeException(e);
        }
    }

现在CustomerServiceTest的init方法如下:

@Before
    public void init() throws  Exception{
        //to do 初始化数据库
        DatabaseHelper.executeSqlFile("sql/customer_init.sql");
    }

   再次运行测试所有的方法,观察单元测试是否全部通过。

    小技巧:在IDEA中,将光标定位在测试方法外部,单击工具栏上的Run(Shift+f10)或Debug(Shift+f9)按钮,可执行所有的测试方法,如果只是将光标定位在某个测试方法内部,那么只能执行当前光标所在的方法。


      到这里,服务层开发完毕了。这些服务会在控制器中得到应用,在下一步我们就来完善控制层。

       总结:

            在进行下一步控制器层完善之前,我们先来进行总结下。

private static final Logger LOGGER = LoggerFactory.getLogger(DatabaseHelper.class);
我使用LOGGER来进行写日志

private static final QueryRunner QUERY_RUNNER;
使用QueryRunner对象来简化sql语句的查询,把表和类对应起来

private static final ThreadLocal<Connection> CONNECTION_HOLDER ;
使用ThreadLocal本地线程对象,使每个线程只有一个Connection,保证线程安全

private static final BasicDataSource DATA_SOURCE;
使用BasicDataSource对象来管理线程池,简化数据库的连接操作。

DatabaseHelper的完整代码如下:

package org.jack.com.helper;

import org.apache.commons.dbcp2.BasicDataSource;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import org.apache.commons.dbutils.handlers.MapListHandler;
import org.jack.com.util.CollectionUtil;
import org.jack.com.util.PropsUtil;
import org.slf4j.LoggerFactory;
import org.slf4j.Logger;

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Properties;

/**
 * Created by jack on 2015/12/26.
 * 数据库操作助手类
 */
public final class DatabaseHelper {
    private static final Logger LOGGER = LoggerFactory.getLogger(DatabaseHelper.class);

    //查询
    private static final QueryRunner QUERY_RUNNER;
    //保证一个线程一个Connection,线程安全
    private static final ThreadLocal<Connection> CONNECTION_HOLDER ;
    //线程池
    private static final BasicDataSource DATA_SOURCE;

    static {
        CONNECTION_HOLDER = new ThreadLocal<Connection>();
        QUERY_RUNNER = new QueryRunner();

        Properties conf = PropsUtil.loadProps("config.properties");
        String driver = conf.getProperty("jdbc.driver");
        String url = conf.getProperty("jdbc.url");
        String username = conf.getProperty("jdbc.username");
        String passwrod = conf.getProperty("jdbc.password");

        DATA_SOURCE=new BasicDataSource();
        DATA_SOURCE.setDriverClassName(driver);
        DATA_SOURCE.setUrl(url);
        DATA_SOURCE.setUsername(username);
        DATA_SOURCE.setPassword(passwrod);
    }

    /**
     * 获取数据库连接
     */
    public static Connection getConnection() {
        Connection conn = CONNECTION_HOLDER.get();//1
        if (conn == null) {
            try {
                conn = DATA_SOURCE.getConnection();
            } catch (SQLException e) {
                LOGGER.error("get connection failure", e);
                throw new RuntimeException(e);
            } finally {
                CONNECTION_HOLDER.set(conn);
            }
        }
        return conn;
    }



    /**
     * 查询实体列表
     */
    public static <T> List<T> queryEntityList(Class<T> entityClass, String sql, Object... params) {
        List<T> entityList;

        try {
            Connection conn = getConnection();
            entityList = QUERY_RUNNER.query(conn, sql, new BeanListHandler<T>(entityClass), params);
        } catch (SQLException e) {
            LOGGER.error("query entity list failure", e);
            throw new RuntimeException(e);
        }
        return entityList;
    }

    /**
     * 查询实体
     */
    public static <T> T queryEntity(Class<T> entityClass,String sql,Object... params){
        T entity;
        try {
            Connection conn=getConnection();
            entity=QUERY_RUNNER.query(conn,sql,new BeanHandler<T>(entityClass),params);
        }catch (SQLException e){
            LOGGER.error("query entity failure",e);
            throw new RuntimeException(e);
        }
        return entity;
    }

    /**
     * 执行查询
     */
    public static List<Map<String,Object>> executeQuery(String sql,Object...params){
        List<Map<String,Object>> result;
        try {
            Connection conn=getConnection();
            result=QUERY_RUNNER.query(conn,sql,new MapListHandler(),params);
        }catch (Exception e){
            LOGGER.error("execute query failure",e);
            throw new RuntimeException(e);
        }
        return result;
    }

    /**
     * 执行更新语句(包括update,inert,delete)
     */
    public static int executeUpdate(String sql,Object... params){
        int rows=0;
        try {
            Connection conn=getConnection();
            rows=QUERY_RUNNER.update(conn,sql,params);
        }catch (SQLException e){
            LOGGER.error("execute update failure",e);
            throw new RuntimeException(e);
        }
        return rows;
    }
    /**
     * 插入实体
     */
    public static <T> boolean insertEntity(Class<T> entityClass,Map<String,Object> fieldMap){
        if(CollectionUtil.isEmpty(fieldMap)){
            LOGGER.error("can not insert entity:fieldMap is empty");
            return false;
        }
        String sql="INSERT INTO "+getTableName(entityClass);
        StringBuilder columns=new StringBuilder("(");
        StringBuilder values=new StringBuilder("(");
        for (String fieldName : fieldMap.keySet()){
            columns.append(fieldName).append(", ");
            values.append("?, ");
        }
        columns.replace(columns.lastIndexOf(", "),columns.length(),") ");
        values.replace(values.lastIndexOf(", "),values.length(),") ");
        sql +=columns + " VALUES " + values;

        Object[] params=fieldMap.values().toArray();
        return executeUpdate(sql,params) == 1;
    }

    /**
     * 更新实体
     * @param entityClass
     * @return boolean
     */
    public static <T> boolean updateEntity(Class<T> entityClass,long id,Map<String,Object> fieldMap){
        if (CollectionUtil.isEmpty(fieldMap)){
            LOGGER.error("can not update entity:fieldMap is empty");
            return false;
        }

        String sql="UPDATE " + getTableName(entityClass) + " SET ";
        StringBuilder columns=new StringBuilder();
        for (String fieldName : fieldMap.keySet()){
            columns.append(fieldName).append("=?, ");
        }
        sql += columns.substring(0,columns.lastIndexOf(", ")) +"WHERE id =?";
        List<Object> paramList =new ArrayList<Object>();
        paramList.addAll(fieldMap.values());
        paramList.add(id);
        Object[] params=paramList.toArray();
        return executeUpdate(sql,params) == 1;
    }

    /**
     * 删除实体
     * @param entityClass
     * @return
     */
    public static <T> boolean deleteEntity(Class<T> entityClass,long id){
        String sql="DELETE FROM "+getTableName(entityClass)+" WHERE id=?";
        return executeUpdate(sql,id) == 1;
    }

    private static String getTableName(Class<?> entityClass){
        return entityClass.getSimpleName();//返回类名
    }

    /**
     * 执行sql文件
     */
    public static void executeSqlFile(String filePath){
        InputStream is=Thread.currentThread().getContextClassLoader().getResourceAsStream(filePath);
        BufferedReader reader=new BufferedReader(new InputStreamReader(is));
        try {
            String sql;
            while((sql=reader.readLine())!=null){
                executeUpdate(sql);
            }
        }catch (Exception e){
            LOGGER.error("execute sql file failure",e);
            throw new RuntimeException(e);
        }
    }

}

CustomerService
CustomerService的代码如下:

package org.jack.com.service;

import org.jack.com.helper.DatabaseHelper;
import org.jack.com.model.Customer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.*;

/**
 * Created by jack on 2015/11/29.
 * 提供客户数据服务
 */
public class CustomerService {
    /*
    * 获取客户列表
    * */
    public List<Customer> getCustomerList(){
        //to do something
        String sql="select * from customer";
        return DatabaseHelper.queryEntityList(Customer.class,sql);

    }

    /*
    * 获取客户
    * */
    public  Customer getCustomer(long id){
        //to do something
        String sql = "SELECT * FROM customer WHERE id = ?";
        return DatabaseHelper.queryEntity(Customer.class, sql, id);
    }

    /*
    * 创建客户
    * */
    public boolean createCustomer(Map<String,Object> fieldMap){
        //to do something

        return DatabaseHelper.insertEntity(Customer.class,fieldMap);
    }

    /*
    * 更新客户
    * */
    public boolean updateCustomer(long id,Map<String,Object> fieldMap){
        //to do something
        return DatabaseHelper.updateEntity(Customer.class,id,fieldMap);
    }

    /*
    * 删除客户
    * */
    public boolean deleteCustomer(long id){
        //to do something
        return DatabaseHelper.deleteEntity(Customer.class,id);
    }

   }

     CustomerServiceTest的完整代码如下:

package org.jack.test;

import org.jack.com.helper.DatabaseHelper;
import org.jack.com.model.Customer;
import org.jack.com.service.CustomerService;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Created by jack on 2015/12/5.
 * 单元测试类CustomerServiceTest
 */
public class CustomerServiceTest {
    private final CustomerService customerService;
    public CustomerServiceTest(){
        customerService = new CustomerService();
    }

    @Before
    public void init() throws  Exception{
        //to do 初始化数据库
        DatabaseHelper.executeSqlFile("sql/customer_init.sql");
    }

    @Test
    public void getCustomerListTest() throws Exception{
        List<Customer> customerList = customerService.getCustomerList();
        Assert.assertEquals(2,customerList.size());
    }

    @Test
    public void getCustomerTest() throws Exception{
        long id = 1;
        Customer customer = customerService.getCustomer(id);
        Assert.assertNotNull(customer);
    }

    @Test
    public void createCustomerTest() throws Exception{
        Map<String,Object> fieldMap = new HashMap<String, Object>();
        fieldMap.put("name","customer100");
        fieldMap.put("contact","John");
        fieldMap.put("telephone","13512345678");
        boolean result = customerService.createCustomer(fieldMap);
        Assert.assertTrue(result);
    }

    @Test
    public void updateCustomerTest() throws  Exception{
        long id = 1;
        Map<String,Object> fieldMap = new HashMap<String, Object>();
        fieldMap.put("contact","Eric");
        boolean result = customerService.updateCustomer(id,fieldMap);
        Assert.assertTrue(result);
    }

    @Test
    public void deleteCustomerTest() throws Exception{
        long id = 1;
        boolean result = customerService.deleteCustomer(id);
        Assert.assertTrue(result);
    }

    /*public static void main(String args[]){
        CustomerServiceTest ct=new CustomerServiceTest();
        try {
            ct.getCustomerListTest();
        }catch (Exception e){
            e.printStackTrace();
        }

    }*/
}

Maven管理的pom.xml目前的代码如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.jack.com</groupId>
    <artifactId>chapter2</artifactId>
    <version>1.0.0</version>
    <packaging>war</packaging>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
    <!--Servlet-->
    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>javax.servlet-api</artifactId>
        <version>3.1.0</version>
        <scope>provided</scope>
    </dependency>

        <!--JUnit-->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <!--<version>3.8.1</version>-->
            <version>4.11</version>
            <scope>test</scope>
        </dependency>

        <!--SLF4J-->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>1.7.7</version>
        </dependency>

        <!--Mysql-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.33</version>
            <scope>runtime</scope>
        </dependency>

        <!--Apache Commons Lang-->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.3.2</version>
        </dependency>

        <!--Apache Commons Collections-->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-collections4</artifactId>
            <version>4.0</version>
        </dependency>


        <!--Apache Commons DbUtils-->
        <dependency>
            <groupId>commons-dbutils</groupId>
            <artifactId>commons-dbutils</artifactId>
            <version>1.6</version>
        </dependency>

        <!--Apache DBCP数据库连接池依赖-->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-dbcp2</artifactId>
            <version>2.0.1</version>
        </dependency>


    </dependencies>

</project>

     接下来进行控制器层的完善。

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值