一,前言
上一节,通过使用Apache DBCP数据库连接池对Connection进行管理
数据库连接池在初始化时将创建一定数量的数据库连接放到连接池中
当用户需要访问数据库时,从连接池中取出一个已建立的空闲连接对象。
使用完毕后,将连接放回连接池中,供下一个请求访问使用。
连接的建立、断开都由连接池自身来管理。
有效的避免了频繁创建数据库连接带来的大量系统开销问题
至此,对于Service层优化的关键部分就完成了
总结一下Service的重构路线:
1,将创建数据库连接的参数提取到properties文件,使用PropsUtil进行读取
是数据库配置信息和代码业务逻辑解耦,可配置
2,将创建数据库链接,关闭数据库连接操作提取到DatabaseHelper类,
在Service层方法中,操作数据库前后分别调用获取连接和关闭连接方法,是代码得到复用
使用DbUtils解决数据库查询时大量重复代码问题,精简了Service层
3,使用ThreadLocal对Connection对象进行线程隔离,将获取,关闭连接的操作合并到DatabaseHelper中
使Service层不需要再关心获取和关闭数据库连接的操作,进一步简化了Service层
4,为了避免频繁创建数据库连接,引入了DBCP连接池对Connection进行"池化"管理
进一步精简了Service层
这一节,按照优化后的Service处理方式,补齐项目剩余的Service方法
二,回顾
在项目设计初期,设计了页面间跳转与操作的URL表,
并根据URL,设计了Servlet
而Service层方法主要服务于Servlet
具体请参考:架构探险-第二章:为Web应用添加业务功能(1)-设计和代码框架
之前的Service优化一直围绕着一个Service方法的实现:就是getCustomerList
/**
* 获取客户列表
*/
public List<Customer> getCustomerList() {
String sql = "select * from customer";
return DatabaseHelper.queryEntityList(Customer.class, sql);
}
DatabaseHelper中的queryEntityList对DbUtils的查询进行了进一步的封装
/**
* 查询实体列表
*/
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;
}
由于使用了DbUtils,使得数据库操作得到了极大简化
在此方法中使用了BeanListHandler来返回客户对象实体列表数据
三,创建查询实体
同样DbUtils也提供了查询单个实体的方法:
/**
* 查询实体
*/
public static <T> T queryEntity(Class<T> entityClass, String sql, Object... params){
T entity = null;
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;
}
可以看到查询单个实体,使用的是BeanHandler对象
四,DbUtils的Handler类介绍
实际上,DbUtils为我们提供了很多类似的Handler,包括:
BeabHandler 返回Bean对象
BeanListHandler 返回List对象
BeanMapHandler 返回Map对象
ArrayHandler 返回Object[]对象
ArrayListHandler 返回List对象
MapHandler 返回Map对象
MapListHandler 返回List对象
ScalarHandler 返回某列的值
ColumnListHandler 返回某列的值列表
KeyedHandler 返回Map对象,需要制定列名
以上这些Handler类都实现了ResultSetHandler
五,强大的查询方法
通过以上对DbUtils中Handler的介绍,可以写出一个万能的查询方法
/**
* 根据sql获取List(对象列名与列值的映射关系)
*/
public static List<Map<String, Object>> executeQuery(String sql, Object... params){
// 保存多条数据的对象字段-值映射关系
List<Map<String, Object>> result = null;
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;
}
此方法借助MapListHandler轻松实现了根据Sql返回任意对象的List
六,插入,更新,删除实体方法
1,插入实体方法
根据上边这个万能的执行语句,创建一个插入实体的方法
/**
* 插入单个实体
*/
public static <T> boolean insertEntity(Class<T> entityClass, Map<String, Object> fieldMap){
// check...
if(CollectionUtil.isEmpty(fieldMap)){
LOGGER.error("can not insert entity: fieldMap is empty");
return false;
}
String sql = "insert into " + getTableName(entityClass);
// 声明columns和values两个StringBuilder用于拼装sql
StringBuilder columns = new StringBuilder("(");// columns : "("fieldName1", "fieldName2", "fieldName3", ...)"
StringBuilder values = new StringBuilder("(");// values : "("?, ?, ?, ...")"
// 循环列参数Map,拼装SQL的columns和values部分,放入对应的StringBuilder中备用
for (String fieldName : fieldMap.keySet()) {
columns.append(fieldName).append(", ");
values.append("?, ");
}
//将最后一个",",换成结束符")",完成columns和values的拼装
columns.replace(columns.lastIndexOf(", "), columns.length(), ")");
values.replace(values.lastIndexOf(", "), values.length(), ")");
// 拼装完整SQL
sql += columns + " VALUES " + values;
// Map转Array
Object[] params = fieldMap.values().toArray();
return executeUpdate(sql, params) == 1;
}
/**
* 获取实体对应的表名
*/
private static String getTableName(Class<?> entityClass) {
return entityClass.getSimpleName();
}
通过executeUpdate(sql, params)执行动态拼装的sql,根据返回条目数,判断执行结果
2,更新,删除实体方法
同理,只要动态拼装update/delete语句,放入executeUpdate执行就可以了
/**
* 更新实体
*/
public static <T> boolean updateEntity(Class<T> entityClass, long id, Map<String, Object> fieldMap){
// check...
if(CollectionUtil.isEmpty(fieldMap)){
LOGGER.error("can not update entity: fieldMap is empty");
return false;
}
String sql = "UPDATE " + getTableName(entityClass) + " SET ";
// 声明columns的StringBuilder用于拼装sql
// columns : "fieldName1" = ?, "fieldName2" = ?, "fieldName3" = ?, ...
StringBuilder columns = new StringBuilder();// 循环列参数Map,拼装SQL的columns部分,放入对应的StringBuilder中备用
for (String fieldName : fieldMap.keySet()) {
columns.append(fieldName).append(" = ?, ");
}
// 拼装完整SQL
sql += columns.substring(0, columns.lastIndexOf(", ")) + " WHERE id = ?";
// 补全参数
List<Object> paramList = new ArrayList<Object>();
paramList.addAll(fieldMap.values());
paramList.add(id);
// Map转Array
Object[] params = paramList.toArray();
return executeUpdate(sql, params) == 1;
}
/**
* 删除实体
*/
public static <T> boolean deleteEntity(Class<T> entityClass, long id) {
String sql = "DELETE FROM " + getTableName(entityClass) + " WHERE id = ?";
return executeUpdate(sql, id) == 1;
}
七,完善Service层
通过以上封装,有了insertEntity,updateEntity,deleteEntity方法
Service对应的数据库操作只需要对他们进行调用即可
CustomerService.java:
public class CustomerService {
private static final Logger LOGGER = LoggerFactory.getLogger(CustomerService.class);
/**
* 获取客户列表
*/
public List<Customer> getCustomerList() {
String sql = "select * from customer";
return DatabaseHelper.queryEntityList(Customer.class, sql);
}
/**
* 获取客户
*/
public Customer getCustomer(long id) {
String sql = "SELECT * FROM customer WHERE id = ?";
return DatabaseHelper.queryEntity(Customer.class, sql, id);
}
/**
* 创建客户
*/
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);
}
}
八,测试新添加的Service
如果运行测试是报错”Unsupported major.minor version 51.0”
请参考:Unsupported major.minor version 51.0问题解决
单元测试:
public class CustomerServiceTest {
private final CustomerService customerService;
public CustomerServiceTest(){
customerService = new CustomerService();
}
@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", "customer1");
fieldMap.put("contact", "Brave");
fieldMap.put("telephone", "13600000000");
fieldMap.put("email", "[email protected]");
boolean result = customerService.createCustomer(fieldMap);
Assert.assertTrue(result);
}
@Test
public void deleteCustomerTest() throws Exception {
long id = 1;
boolean result = customerService.deleteCustomer(id);
Assert.assertTrue(result);
}
}
测试运行结果正常
九,结尾
本节补全了CustomerService的增删改查方法
介绍了DbUtils提供的Handler对象,使用MapListHandler创建了万能的执行方法executeQuery
通过动态拼装增删改查sql,传入executeQuery中执行,进一步封装增删改查操作
极大地简化了CustomerService的代码,逻辑清晰,具有极强的复用性
最后执行Junit测试了方法的正确性
这里还存在一个问题,当我们执行完Junit测试时,deleteCustomerTest的测试方法使id=1的数据被删除
这导致了下次执行Junit时这条数据就不存在了,所以针对这个问题,下一步将对Junit做进一步修改
以便适合于正式项目的开发和测试流程