从两行到一行-利用反射机制自动完成 model–table的映射
在上一文中,已经完成了对getCustomerList方法的优化。但是同样也暴露出来了问题。
在此方法中,直接使用String常量作为sql语句
String sql = "SELECT * FROM customer";
这给数据库的操作带来了限制,因为是静态的常量,无法动态的实现数据库的查询。随着开发的推进,数据库里面不可能只有customer一张表,针对新表的查询必须要重写sql语句,这显然不复合代码重用的原则。
如何解决这个问题呢?对,就是前几篇博客写的反射机制。我们可以通过反射机制获取表名,再针对相应的表进行数据库的操作。
/**
* 一个更为强大的
* 执行查询语句
*/
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 (SQLException e) {
LOGGER.error("execute query failure", e);
throw new RuntimeException(e);
}
return result;
}
如上是一个更为强大的查询语句,输入一个SQL与动态参数,输出一个List对象,其中Map表示列名与列值的对应的映射关系。
除了查询以外,还包括集中更新语句,例如updata,insert,delete等。提供一个更为通用的执行更新语句的方法。
/**
* 一个通用的更新(包括update,insert,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;
}
该方法返回受影响的记录数,即更新了多少记录。根据这个通用的方法,可以分别提供三种具体的数据库更新操作。
/**
* 插入实体
* 注意SQL语句书写规范,尤其是要预留空格
*/
public static <T> boolean insertEntity(Class<T> entityClass, Map<String, Object> fieldMap) {
if (CollectionUtil.isEmpty(fieldMap)) {
LOGGER.error("can not insert entity:fieldMap id 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("?, ");
}
/**拼接插入操作语句:
* INSERT INTO table_name (列1, 列2,...) VALUES (值1, 值2,....)
* replace(int start, int end, String str)
* Replaces the characters in a substring of this sequence with characters in the specified String
*/
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;
}
/**
* 更新实体
*/
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 id empty!");
return false;
}
String sql = "UPDATE " + getTableName(entityClass) + " SET ";
StringBuilder columns = new StringBuilder();
for (String fieldName : fieldMap.keySet()) {
columns.append(fieldName).append("=?, ");
}
/**
* 拼接更新操作语句:
* UPDATE 表名称 SET 列名称 = 新值 WHERE 列名称 = 某值
*/
sql += columns.substring(0, columns.lastIndexOf(", ")) + "WHERE id=?";
List<Object> paramsList = new ArrayList<Object>();
paramsList.addAll(fieldMap.values());
paramsList.add(id);
Object[] params = paramsList.toArray();
return executeUpdate(sql, params) == 1;
}
/**
* 删除实体
* DELETE FROM 表名称 WHERE 列名称 = 值
*/
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("executeSqlFile sql file failure ", e);
}
}
有了以上的insertEntity、uodateEntity、deleteEntity,就可以在CustomerService中实现所需的功能了。
/**
* 创建客户
*/
public boolean createCustomer(Map<String, Object> fieldMap){
return DataBaseHelper.insertEntity(Customer.class, fieldMap);
}
/**
* 更新客户
*/
public boolean updateCustomer(long id, Map<String, Object> fieldMap){
return DataBaseHelper.updateEntity(Customer.class, id, fieldMap);
}
/**
* 删除客户
*/
public boolean deleteCustomer(long id){
return DataBaseHelper.deleteEntity(Customer.class, id);
}
两行到一行,done。进一步封装了数据库的操作,在service层真正做到了只需要关心具体的业务。
连接“池”化
现在的CustomerService的实现已经是相当简洁了。那么着手思考下一个问题:
像上面这样,每次需要连接数据库时,都是调用getConnection方法,在数据库操作完成之后,还需要调用closeConnection来关闭数据连接。虽然关闭数据库的操作已经在DataBaseHelper中进行了封装,但是考虑到如果频繁的调用getConnection方法就会频繁的创建数据库的连接,从而造成大量的开销–数据库的连接时有限的。因此,需要考虑一个新的策略来优化数据库的连接。
数据库连接是一种关键的、有限的、昂贵的资源,这一点在多用户的网页应用程序中体现得尤为突出。对数据库连接的管理能显著影响到整个应用程序的伸缩性和健壮性,影响到程序的性能指标。数据库连接池正是针对这个问题提出来的。
数据库连接池负责分配、管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连接,而不是再重新建立一个;释放空闲时间超过最大空闲时间的数据库连接来避免因为没有释放数据库连接而引起的数据库连接遗漏。这项技术能明显提高对数据库操作的性能。
也就是说,我们需要一个数据库连接池,引入以下依赖:
<!--Dbcp2-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-dbcp2</artifactId>
<version>2.0.1</version>
</dependency>
在DataBase使用该依赖,使得数据量的连接“池”化,DataBaseHelper最终代码如下:
package org.smart4j.chapter2.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.slf4j.LoggerFactory;
import org.smart4j.chapter2.util.CollectionUtil;
import org.smart4j.chapter2.util.PropsUtil;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Properties;
/**java
* Created by Tree on 2017/3/27.
* 将数据库连接的操作分离出来,这样避免在每一个service里面都要写
* 很长的数据库连接的代码
*
* 03月29号,引入commons-dacp2依赖,将数据库连接池化:
* 静态初始化 BasicDataSource,之后就可以删除“关闭数据库”的方法。
*/
public class DataBaseHelper {
private static final org.slf4j.Logger LOGGER = LoggerFactory.getLogger(PropsUtil.class);
private static final QueryRunner QUERY_RUNNER ;
private static final ThreadLocal<Connection> CONNECTION_THREAD_LOCAL;
private static final BasicDataSource DATA_SOURCE;
/**
* 使用静态代码块初始化配置项定义的一些常量
*/
static {
CONNECTION_THREAD_LOCAL=new ThreadLocal<Connection>();
QUERY_RUNNER=new QueryRunner();
Properties conf = PropsUtil.loadProps("config.properties");
String drive = conf.getProperty("jdbc.driver");
String url= conf.getProperty("jdbc.url");
String username = conf.getProperty("jdbc.username");
String password = conf.getProperty("jdbc.password");
DATA_SOURCE=new BasicDataSource();
DATA_SOURCE.setDriverClassName(drive);
DATA_SOURCE.setUrl(url);
DATA_SOURCE.setUsername(username);
DATA_SOURCE.setPassword(password);
}
/**
* 获取数据库连接
*/
public static Connection getConnection() {
Connection conn = CONNECTION_THREAD_LOCAL.get();
if(conn==null) {
try {
conn = DATA_SOURCE.getConnection();
} catch (SQLException e) {
LOGGER.error("get connection failure", e);
throw new RuntimeException(e);
}finally {
CONNECTION_THREAD_LOCAL.set(conn);
}
}
return conn;
}
/**
* 关闭数据库连接
* 引入连接池之后就可以关闭这个方法了
*/
// public static void closeConnection() {
// Connection conn = CONNECTION_THREAD_LOCAL.get();
// if (conn != null) {
// try {
// conn.close();
// } catch (SQLException e) {
// LOGGER.error("close connection failure", e);
// throw new RuntimeException(e);
// } finally {
// CONNECTION_THREAD_LOCAL.remove();
// }
// }
// }
/**
* 查询客户列表:使用工具DbUtil
* Object... params:“Varargs”机制。借助这一机制,可以定义能和多个实参相匹配的形参。
* 但是该方法每次操作都需要创建并关闭Connection,Connection对于开发人员并不完全透明
* 通过引入本地线程变量CONNECTION_THREAD_LOCAL提供线程安全的保证。
*/
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 (SQLException e) {
LOGGER.error("execute query failure", e);
throw new RuntimeException(e);
}
return result;
}
/**
* 一个通用的更新(包括update,insert,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;
}
/**
* 插入实体
* 注意SQL语句书写规范,尤其是要预留空格
*/
public static <T> boolean insertEntity(Class<T> entityClass, Map<String, Object> fieldMap) {
if (CollectionUtil.isEmpty(fieldMap)) {
LOGGER.error("can not insert entity:fieldMap id 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("?, ");
}
/**拼接插入操作语句:
* INSERT INTO table_name (列1, 列2,...) VALUES (值1, 值2,....)
* replace(int start, int end, String str)
* Replaces the characters in a substring of this sequence with characters in the specified String
*/
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;
}
/**
* 更新实体
*/
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 id empty!");
return false;
}
String sql = "UPDATE " + getTableName(entityClass) + " SET ";
StringBuilder columns = new StringBuilder();
for (String fieldName : fieldMap.keySet()) {
columns.append(fieldName).append("=?, ");
}
/**
* 拼接更新操作语句:
* UPDATE 表名称 SET 列名称 = 新值 WHERE 列名称 = 某值
*/
sql += columns.substring(0, columns.lastIndexOf(", ")) + "WHERE id=?";
List<Object> paramsList = new ArrayList<Object>();
paramsList.addAll(fieldMap.values());
paramsList.add(id);
Object[] params = paramsList.toArray();
return executeUpdate(sql, params) == 1;
}
/**
* 删除实体
* DELETE FROM 表名称 WHERE 列名称 = 值
*/
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("executeSqlFile sql file failure ", e);
}
}
}
控制层–以CustomerServlet为例
package org.smart4j.chapter2.controller;
import org.smart4j.chapter2.model.Customer;
import org.smart4j.chapter2.service.CustomerService;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;
/**
* Created by Tree on 2017/3/27.
* 进入客户列表界面
*/
@WebServlet("/customer")
public class CustomerServlet extends HttpServlet {
private CustomerService customerService;
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
List<Customer> customerList = customerService.getCustomerList();
req.setAttribute("customerList",customerList);
req.getRequestDispatcher("/WEB-INF/view/customer.jsp").forward(req,resp);
}
@Override
public void init() throws ServletException {
customerService=new CustomerService();
}
}
视图层–以customer.jsp为例(偷懒版)
<%--
Created by IntelliJ IDEA.
User: Tree
Date: 2017/3/27
Time: 9:08
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<c:set var="BASE" value="${pageContext.request.contextPath}"/>
<html>
<head>
<title>客户管理</title>
</head>
<body>
<h1>客户列表</h1>
<table>
<tr>
<th>客户名称</th>
<th>联系人</th>
<th>电话号码</th>
<th>邮箱地址</th>
<th>操作</th>
</tr>
<c:forEach var="customer" items="${customerList}">
<tr>
<td>${customer.name}</td>
<td>${customer.contact}</td>
<td>${customer.telephone}</td>
<td>${customer.email}</td>
<td>
<a href="${BASE}/customer_edit?id=${customer.id}">编辑</a>
<a href="${BASE}/customer_delete?id=${customer.id}">删除</a>
</td>
</tr>
</c:forEach>
</table>
</body>
</html>
结果展示
干!昨晚还好好的,今天上午就抽风了;
算了,先去吃个午饭。
吃饭回来了,发现自己在servlet上打了断点,去掉之后就可以了。好蠢。