您的位置:首页 > 编程语言 > Java开发

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

2015-12-27 14:03 615 查看
  在前面我们搭建了一个客户管理模块的代码框架,在代码中有很大的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>

     接下来进行控制器层的完善。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  java serverlet mvc