《架构探险--从零开始写Java Web框架》学习笔记(1)
其他人笔记:
1、https://www.jianshu.com/p/9906b58b55d6 (简要版)
2、https://www.jianshu.com/p/3ac5f1fe6bc7 (简要版)
第一章 从一个简单的Web应用开始
1.1、使用idea创建Maven项目
1.1.1、创建idea项目
1.1.2、调整Maven配置
pom文件
${project.artifactId}
org.apache.maven.plugins
maven-compiler-plugin
1.8
UTF-8
org.springframework.boot
spring-boot-maven-plugin
org.apache.maven.plugins
maven-surefire-plugin
2.18.1
true
1.2、 搭建Web项目框架
1.2.1、 转为Java Web项目
将maven项目调整为web项目结构
需要三步即可实现:
- 在main目录下,添加webapp目录
- 在webapp目录下添加WEB-INF目录
- 在WEB-INF目录下,添加web.xml文件即可
提示,点击Configure->OK即可
然后在web.xml中添加代码
1.2.2、 添加java web的maven依赖
由于web项目是需要打war包的,所以在pom里设置packaging为war(默认为jar)
war
接下来添加servlet、JSP、JSTL依赖
javax.servlet
javax.servlet-api
3.1.0
provided
javax.servlet.jsp
jsp-api
2.2
provided
javax.servlet
jstl
1.2
runtime
Maven依赖的三坐标(groupId、artifactId、version必须提供)
如果
赖只参与编译不需要打包,就可以scope为provided
tomcat自带servlet以及JSP对应jar包
如果依赖只是运行时需要,但无需参与编译,可以将其scope设置为runtime。
1.3、编写一个简单的web应用
1.3.1、编写servlet类
我们要做的事情:写一个HelloServlet,接受GET类型的/hello请求,转发到/WEB-INF/jsp/hello.jsp页面
-
在java目录下创建一个名为org.yankun.chapter1的包
-
创建类如下
@WebServlet// javax.servlet.annotation.WebServlet;包下的 fixme Servlet 3.0规范提供的此注解,3.0开始支持全部使用注解(实现‘零配置’的web.xml) public class HelloServlet extends HttpServlet { // 按 Alt+Insert @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String currentTime = dateFormat.format(new Date()); request.setAttribute("currentTime",currentTime); request.getRequestDispatcher("/WEB-INF/jsp/hello.jsp").forward(request,response); } }
- 继承HttpServlet,让它成为servlet类
- 覆盖父类的doGet方法,用于接收GET请求
- 在doGet方法中数据,将其放到HttpServletRequest对象然后转发到/WEB-INF/jsp/hello.jsp
- 使用WebServlet注解并配置请求路径,对外发布Servlet服务
1.3.2、编写JSP页面
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
Hello--架构探险
Hello! jsp
当前时间: ${currentTime}
1.4、 让Web应用跑起来(运行web应用)
1.4.1、在idea中配置Tomcat
1.
首先在工具栏找到edit configurations,弹出Run/Debug对话框
2.
单机左上角的+号,选择Tomcat Servlet->local选项
3.
输入tomcat的name,取消勾选after launch
4.
Application server下的Configure按钮,配置选择一个tomcat(自己安装的)
1.4.2、 关于使用tomcat
org.apache.tomcat.maven
tomcat7-maven-plugin
2.1
/${project.artifactId}
不要打错了
里的路径,任何一个符号错了就不可以,这里引用的值就是项目的artifactId,此项目的artifactId就是chapter1。
的作用是当前web项目的url访问入口,即 http://localhost:8080/chapter1/
若设置为/ 则访问url为 http://localhost:8080/
1.4.3、 以debug的方式运行
然后添加一个maven的configuration
在common line输入tomcat7:run,点击OK然后RUN或者DEBUG 热部署了就可以访问了
部署完成之间输入url + @WebServlet的值
如:http://localhost:8080/chapter1/hello
1.5、将代码放入Git仓库中
1.5.1、编写 .gitignore文件
在项目的根目录下,添加一个.gitignore文件
# Build and Release Folders
bin-debug/
bin-release/
[Oo]bj/
[Bb]in/
# Other files and folders
.settings/
# Executables
*.swf
*.air
*.ipa
*.apk
# Project files, i.e. `.project`, `.actionScriptProperties` and `.flexProperties`
# should NOT be excluded as they contain compiler settings and other important
# information for Eclipse / Flash Builder.
**/mvnw
**/mvnw.cmd
**/.mvn
**/target/
.idea
.gitignor
1.5.2、 提交本地Git仓库
1.5.3、推送远程Git仓库
总结一下:本地仓库有文件,远程仓库也有文件,正确姿势:
1,git remote add origin 远程仓库地址
2,git pull origin master --allow-unrelated-histories
3,git branch --set-upstream-to=origin/master master
4,git push
————————————————
版权声明:本文为CSDN博主「jack22001」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/jack22001/article/details/87946037
git push后出现错误 ![rejected] master -> master(non-fast-forward) error:failed to push some refs to XXX
是因为你在码云创建的仓库有ReadMe文件,而本地没有,造成本地和远程的不同步,
那么有两种方案可以解决:
1、one :
本地没有ReadMe文件,那么就在本地生成一个:git pull --rebase origin master ???本地生成ReadMe文件
git push origin master2、two:
那我就强制上传覆盖远程文件,
git push -f origin master
(这个命令在团队开发的时候最好不要用,否则可能会有生命危险)摘自博客: https://blog.csdn.net/awm_kar98/article/details/89463117
第二章 为Web应用添加业务功能
第一章我们只是完成了一个简单的Web应用,但是目前只能通过Servlet处理简单的请求,并没有实现业务逻辑。
第二章我们将在这个基础上增加一些业务功能,将涉及如下知识:
1、如何进行需求分析;
2、如何进行系统设计;
3、如何编写应用程序。
此外,我们将使用一些代码重构的技巧,不断优化现有的代码。好的程序不是一次性写出来的,而是不断地“改”出来的,这里的“改”就是重构。
2.1、 需求分析与系统设计
。。。。。。略(具体可以去看书)
这一章书中举了一个简单的例子,完全基于Servlet API,实现顾客信息的增删改查
2.2、动手开发web应用
2.2.1、 创建数据
创建一个数据库,编码使用UTF-8,
2.2.2、 准备开发环境
本应该再新建一个项目,叫做chapter2,一个web项目,和第一章的项目构建一样
但是本人为了省事就直接在上一章中的代码中改了
<?xml version="1.0" encoding="UTF-8"?>
4.0.0
org.yankun
chapter1
1.0-SNAPSHOT
war
javax.servlet
javax.servlet-api
3.1.0
provided
javax.servlet.jsp
jsp-api
2.2
provided
javax.servlet
jstl
1.2
runtime
junit
junit
4.13
org.slf4j
slf4j-log4j12
1.7.25
mysql
mysql-connector-java
8.0.19
runtime
org.apache.commons
commons-lang3
3.3.2
org.apache.commons
commons-collections4
4.1
commons-dbutils
commons-dbutils
1.6
org.apache.commons
commons-dbcp2
2.0.1
${project.artifactId}
org.apache.maven.plugins
maven-compiler-plugin
1.8
UTF-8
org.springframework.boot
spring-boot-maven-plugin
org.apache.maven.plugins
maven-surefire-plugin
2.18.1
true
此时pom文件如上
2.2.3、 编写模型层
新建controller、model、service三个package
然后在model下新建一个Customer类
类中有id、name、contact、telephone、email、remark属性,然后每个属性都有自己的getter/setter方法
package org.yankun.chapter1.model;
/**
* @Author qiyue
* @Date 2020/11/9 14:42
*/
public class Customer {
/**
* ID
*/
private long id;
/**
* 客户名称
*/
private String name;
/**
* 联系人
*/
private String contact;
/**
* 电话号码
*/
private String telephone;
/**
* 邮箱
*/
private String email;
/**
* 备注
*/
private String remark;
在数据库中新建一个表,然后在表中添加几条数据
CREATE TABLE `customer` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '自增主键',
`name` varchar(255) COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '客户名称',
`contact` varchar(255) COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '联系方式',
`telephone` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT '联系方式',
`email` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT '邮箱地址',
`remark` text COLLATE utf8_unicode_ci COMMENT '备注',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
2.2.4、编写控制层
在controller控制层下新建几个类CustomerCreateServlet CustomerDeleteServlet CustomerEditServlet CustomerServlet CustomerShowServlet
每个类都继承HttpServlet,然后override doGet、doPost方法
@WebServlet("/customer_create")
public class CustomerCreateServlet extends HttpServlet {
/**
*进入 创建页面 的请求
* @param req
* @param resp
* @throws ServletException
* @throws IOException
*/
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// todo 类似这样
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
super.doPost(req, resp);
}
2.2.5、编写服务层
在service层,新建一个CustomerService类,然后写上书中的方法,返回值都是null/false
大概代码如下:
public List getCustomerList(String keyword) throws SQLException {}
public List getCustomerList(){}
public Customer getCustomer(long id){
// TODO
return null;
}
public boolean createCustomer(Map fieldMap){}
public boolean updateCustomer(long id,Map fieldMap){}
//删除用户
public boolean deleteCustomer(long id){
return DatabaseHelper.deleteEntity(Customer.class,id);
}
2.2.6、编写单元测试
使用Junit测试,在pom中加入依赖
在test/java目录下新建一个单元测试类com.gem.chapter2.test.CustomerServiceTest这个是项目中src/java的文件结构是一样的,包名也要一致,也是pom中的< groupId > 以及 < artifactId >
然后敲一下几个test,没有难的地方
junit
junit
4.13
package org.yankun.chapter1.controller;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.yankun.chapter1.helper.DatabaseHelper;
import org.yankun.chapter1.model.Customer;
import org.yankun.chapter1.service.CustomerService;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 单元测试
* @Author qiyue
* @Date 2020/11/9 19:18
*/
public class CustomerServiceTest {
private final CustomerService customerService;
private final static Logger LOGGER = LoggerFactory.getLogger(CustomerServiceTest.class);
public CustomerServiceTest(){
customerService = new CustomerService();
}
@Before
public void init() throws IOException {
//按行读取sql语句
// TODO: 2022/1/17
}
// 执行之前先注掉上边Before里面的执行sql的两句代码
@Test
public void testLog4j(){
LOGGER.info("programer processiong");
try{
System.out.println(1/0);
}catch (Exception E){
LOGGER.error("error");
}
LOGGER.debug("start debug detail......");
}
@Test
public void getCustomerListTest(){
List customerList = customerService.getCustomerList();
Assert.assertEquals(0, customerList.size()); // TODO: 2022/1/17 调通数据库连接,此单测通过
}
@Test
public void getCustomerTest(){
long id = 1;
Customer customer = customerService.getCustomer(1);
Assert.assertNotNull(customer);
}
@Test
public void createCustomerTest(){
Map fieldMap = new HashMap<>();
fieldMap.put("name","customer3");
fieldMap.put("contact","JOJO");
fieldMap.put("telephone","10086");
boolean result = customerService.createCustomer(fieldMap);
Assert.assertTrue(result);
}
@Test
public void deleteCustomerTest(){
long id = 1;
System.out.println(double.class);
// boolean result = customerService.deleteCustomer(id);
// Assert.assertTrue(result);
}
2.2.7、 编写视图层
2.3、细节完善与代码优化
2.3.1、完善服务层
log4j.properties的文件
然后就是配置文档,在src/main/resources
目录下创建一个名为log4j.properties的文件内容如下
### set log levels ###
log4j.rootLogger = DEBUG,stdout,D,E
# 配置日志信息输出目的地
log4j.appender.stdout = org.apache.log4j.ConsoleAppender
# Target是输出目的地的目标
log4j.appender.stdout.Target = System.out
# 指定日志消息的输出最低层次
log4j.appender.stdout.Threshold = INFO
# 定义名为stdout的输出端的layout类型
log4j.appender.stdout.layout = org.apache.log4j.PatternLayout
# 如果使用pattern布局就要指定的打印信息的具体格式ConversionPattern
log4j.appender.stdout.layout.ConversionPattern = [%-5p] %d{yyyy-MM-dd HH:mm:ss} %l%m%n
# 名字为D的对应日志处理
log4j.appender.D = org.apache.log4j.DailyRollingFileAppender
# File是输出目的地的文件名
log4j.appender.D.File = ${user.home}/logs/book_debug.log
#false:默认值是true,即将消息增加到指定文件中,false指将消息覆盖指定的文件内容
log4j.appender.D.Append = true
log4j.appender.D.Threshold = DEBUG
log4j.appender.D.layout = org.apache.log4j.TTCCLayout
# 名字为E的对应日志处理
log4j.appender.E = org.apache.log4j.DailyRollingFileAppender
log4j.appender.E.File = ${user.home}/logs/book_error.log
log4j.appender.E.Append = true
log4j.appender.E.Threshold = ERROR
log4j.appender.E.layout = org.apache.log4j.PatternLayout
log4j.appender.E.layout.ConversionPattern = %-d{yyyy-MM-dd HH:mm:ss} [ %t:%r ] - [ %p ] %m%n
###################################################################################
#你的指定包下的配置
log4j.logger.com.gem = DEBUG,A1
log4j.appender.A1 = org.apache.log4j.DailyRollingFileAppender
log4j.appender.A1.File = LOG//A1_app_debug.log
log4j.appender.A1.Append = true
log4j.appender.A1.Threshold = DEBUG
log4j.appender.A1.layout = org.apache.log4j.TTCCLayout
log4j.properties里的内容解释:
rootLogger = DEBUG,stdout,D,E
#配置根logger
基本格式为 log4j.rootLogger = LEVEL, appenderName1 , appenderName2 ,只能有一个等级的哦,不能存在多个等级,否则会报错
LEVEL优先级从高到低依次为:OFF、FATAL、ERROR、WARN、INFO、DEBUG、TRACE、 ALL
- TRACE 很低的日志级别,一般不会使用。
- DEBUG主要用于开发过程中打印一些运行信息。
- INFO 消息在粗粒度级别上突出强调应用程序的运行过程。打印一些你感兴趣的或者重要的信息,这个可以用于生产环境中输出程序运行的一些重要信息,但是不能滥用,避免打印过多的日志。
- WARN 表明会出现潜在错误的情形,有些信息不是错误信息,但是也要给程序员的一些提示。
- ERROR 指出虽然发生错误事件,但仍然不影响系统的继续运行。打印错误和异常信息,如果不想输出太多的日志,可以使用这个级别。
- FATAL 指出每个严重的错误事件将会导致应用程序的退出。这个级别比较高了。重大错误,这种级别你可以直接停止程序了。
但是OFF是一切都不显示,ALL是一切都显示。如果将log level设置在某一个级别上,那么比此级别优先级高或者相同的log才能打印出来
然后对于每一个appenderName都要配置一下
-
log4j.appender.stdout
配置输出目的地可以从下面选
- rg.apache.log4j.ConsoleAppender(控制台)
- org.apache.log4j.FileAppender(文件)
- org.apache.log4j.DailyRollingFileAppender(每天产生一个日志文件)
如果是输出目的是org.apache.log4j.ConsoleAppender
- Target是输出目的地的目标
log4j.appender.stdout.Target = System.out
- Threshold是输出的最低层次
log4j.appender.stdout.Threshold = INFO
-
layout是输出类型-- 可以从下面选:
- org.apache.log4j.HTMLLayout(以HTML表格形式布局)
- org.apache.log4j.PatternLayout(可以灵活地指定布局模式)
- org.apache.log4j.SimpleLayout(包含日志信息的级别和信息字符串)
- org.apache.log4j.TTCCLayout(包含日志产生的时间、线程、类别等等信息)
选择
org.apache.log4j.PatternLayout
,然后需要指定打印格式log4j.appender.stdout.layout.ConversionPattern = [%-5p] %d{yyyy-MM-dd HH:mm:ss} %l%m%n
如果是输出目的是org.apache.log4j.DailyRollingFileAppender
-
File是输出目的地的名字
log4j.appender.D.File = ${user.home}/logs/book_debug.log
这个${user.home}就是电脑的当前的用户名,就是
C:\Users\username
-
剩下的
Append
Threshold
layout
是一样的
在类中使用日志
在properties中指定包下新建一个类,
然后类中写到
private final static Logger logger = LoggerFactory.getLogger(LogTest.class);
public static void main(String [] args){
logger.info("com.turtle.log1.LogTest的Info级别日志");
logger.debug("com.turtle.log1.LogTest的Debug级别日志");
}
就会发现在控制台出现
[INFO ] 2020-11-14 10:32:50 com.turtle.log1.LogTest.main(LogTest.java:12)com.turtle.log1.LogTest的Info级别日志
的内容,需要注意的是 LoggerFactory.getLogger的参数是当前类的class,比如我的就是LogTest
而这里出现了INFO信息是因为设置的等级是INFO,DEBUG等级低于INFO,无法显示
如果使用的是logger.error()
那么在C:\Users\username\logs文件夹下的还会有文件内容
参考文章:https://www.cnblogs.com/zhh19981104/p/12133730.html#_label1
本文部分转载自:
[(87条消息) 架构探险 从零开始写javaweb框架-黄勇第二章笔记]_柒月饰珋的博客-CSDN博客
org.apache.commons
commons-lang3
3.3.2
org.apache.commons
commons-collections4
4.1
开始写代码:
1、实现CustomerService里的getCustomerList()方法
jdbc.driver=com.mysql.cj.jdbc.Driver
# jiagoutanxian-1 这是数据名称,在下面的字符串中必须改成自己的
jdbc.url=jdbc:mysql://106.14.106.57:3306/jiagoutanxian-1?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC&nullCatalogMeansCurrent=true
jdbc.username=root
jdbc.password=你自己的密码
既然属性文件config.properties已经准备好了,我们则需要有一个类来读取这个文件,我们使用PropsUtil工具类来完成这件事:(放在Util包下的)
package org.yankun.chapter1.util;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
/**属性文件工具类
* @Author qiyue
* @Date 2020/11/10 9:58
*/
public class PropsUtil {
private static final Logger LOGGER = LoggerFactory.getLogger(PropsUtil.class);
/**
* 加载属性文件
* @param fileName 属性文件名
* @return 属性类
*/
public static Properties loadProps(String fileName){
Properties properties =null;
InputStream is = null;
try {
is = Thread.currentThread().getContextClassLoader().getResourceAsStream(fileName);
if (is==null){
throw new FileNotFoundException(fileName+" file is not found");
}
properties = new Properties();
properties.load(is);
} catch (IOException e) {
LOGGER.error("load properties file failure",e);
} finally {
if (is!=null){
try {
is.close();
} catch (IOException e) {
LOGGER.error("close input stream failure",e);
}
}
return properties;
}
}
/**
* 获得字符型属性(默认为空字符串)
* @param properties
* @param key
* @return
*/
public static String getString(Properties properties,String key){
return getString(properties,key,"");
}
/**
* 获取字符串属性(可指定默认值)
* @param properties
* @param key
* @param defaultValue
* @return
*/
public static String getString(Properties properties,String key,String defaultValue){
String value =defaultValue;
if (properties.containsKey(key)){
value = properties.getProperty(key);
}
return value;
}
/**
* 获取数值型属性(默认为0)
* @param properties
* @param key
* @return
*/
public static int getInt(Properties properties,String key){
return getInt(properties,key,0);
}
/**
* 获取数值型属性,可指定默认值
* @param properties
* @param key
* @param defaultValue
* @return
*/
public static int getInt(Properties properties,String key,int defaultValue){
int value =defaultValue;
if (properties.containsKey(key)){
value = CastUtil.caseInt(properties.getProperty(key));
}
return value;
}
/**
* 获取布尔型属性(默认为false)
* @param properties
* @param key
* @return
*/
public static boolean getBoolean(Properties properties,String key){
return getBoolean(properties,key,false);
}
/**
* 获取布尔型属性,可指定默认值
* @param properties
* @param key
* @param b
* @return
*/
private static boolean getBoolean(Properties properties, String key, boolean b) {
boolean value = b;
if (properties.containsKey(key)){
value = CastUtil.castBoolean(properties.getProperty(key));
}
return value;
}
}
其中,最关键的是 loadProps方法,我们只需传递一个属性文件的名称,即可返回一个Properties对象,然后再根据getString、 getInt、getBoolean这些方法由 key获取指定类型的 value,同时也可指定defaultValue 作为默认值。
在PropsUtil类中,我们用到了CastUtil类,该类是为处理一些数据转型操作而准备的,代码如下:
package org.yankun.chapter1.util;
/**
* 转型操作工具类
* @Author qiyue
* @Date 2020/11/10 10:19
*/
public class CastUtil {
/**
* 转为String型
* @param obj
* @return
*/
public static String castString(Object obj){
return castString(obj,"");
}
/**
* 转为String型号(提供默认值)
* @param obj
* @param defaultValue
* @return
*/
public static String castString(Object obj,String defaultValue){
return obj!=null?String.valueOf(obj):defaultValue;
}
/**
* 转为double
* @param object
* @return
*/
public static double castDouble(Object object){
return castDouble(object,0);
}
/**
* 转为double 可指定默认值
* @param object
* @param defaultValue
* @return
*/
public static double castDouble(Object object,double defaultValue){
double doubleValue = defaultValue;
if (object!=null){
String stringValue = castString(object);
if (StringUtil.isNotEmpty(stringValue)){
try {
doubleValue = Double.parseDouble(stringValue);
}catch (NumberFormatException e){
doubleValue = defaultValue;
}
}
}
return doubleValue;
}
/**
* 转为long型
* @param object
* @return
*/
public static long castLong(Object object){
return castLong(object,0);
}
/**
* 转为long型,提供默认值
* @param object
* @param defaultValue
* @return
*/
public static long castLong(Object object,long defaultValue){
long longValue = defaultValue;
if (object!=null){
String stringValue = castString(object);
if (StringUtil.isNotEmpty(stringValue)){
try {
longValue = Long.parseLong(stringValue);
}catch (NumberFormatException e){
longValue = defaultValue;
}
}
}
return longValue;
}
/**
* 转为int型
* @param object
* @return
*/
public static int caseInt(Object object) {
return caseInt(object,0);
}
/**
* 转为int型,提供默认值
* @param object
* @param defaultValue
* @return
*/
public static int caseInt(Object object,int defaultValue){
int intValue = defaultValue;
if (object!=null){
String stringValue = castString(object);
if (StringUtil.isNotEmpty(stringValue)){
try{
intValue = Integer.parseInt(stringValue);
}catch (NumberFormatException e){
intValue = defaultValue;
}
}
}
return intValue;
}
/**
* 转为Boolean
* @param object
* @return
*/
public static boolean castBoolean(Object object){
return castBoolean(object,false);
}
/**
* 转为boolean型,提供默认值
* @param object
* @param defaultValue
* @return
*/
public static boolean castBoolean(Object object,boolean defaultValue){
boolean booleanValue = defaultValue;
if (object!=null){
booleanValue = Boolean.parseBoolean(castString(object));
}
return booleanValue;
}
}
2、做一个CollectionUtil
以上我们只是对Apache Commons类库做了一个简单的封装。同理,也可以做一个CollectionUtil,用于提供一些集合操作,代码如下:
package org.yankun.chapter1.util;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.MapUtils;
import java.util.Collection;
import java.util.Map;
/**
* @Author qiyue
* @Date 2020/11/10 11:11
*/
public class CollectionUtil {
/**
* 判断collection是否为空
* @param collection
* @return
*/
public static boolean isEmpty(Collection<?>collection){
return CollectionUtils.isEmpty(collection);
}
/**
* 判断collection是否非空
* @param collection
* @return
*/
public static boolean isNotEmpty(Collection<?>collection){
return !isEmpty(collection);
}
/**
* 判断MAP是否为空
* @param map
* @return
*/
public static boolean isEmpty(Map<?,?>map){
return MapUtils.isEmpty(map);
}
/**
* 判断MAP是否非空
* @param map
* @return
*/
public static boolean isNotEmpty(Map<?,?>map){
return !isEmpty(map);
}
}
3、我们一口气写了四个工具类,
每个工具类的分工各不相同,它们在后面还会经常用到,我们也会不断地完善这些工具类。
现在回到CustomerService,我们需要在该类中执行数据库操作,也就是需要编写一些JDBC的代码,首先使用PropsUtil 读取 config.properties配置文件,获取与JDBC相关的配置项。
我们不妨在 CustomerService中为这些配置项定义一些常量,并提供-一个“静态代码块”来初始化这些常量,就像下面这样:
public class CustomerService {
private static final Logger LOGGER = LoggerFactory.getLogger(CustomerService.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) {
System.out.println("can not load jdbc driver");
LOGGER.error("can not load jdbc driver", e);
}
}
}
4、以getCustomerList为例,我们可以这样写JDBC代码:
/*
* 获取客户列表
* @param keyword
* @return
*/
public List getCustomerList(String keyword) throws SQLException {
Connection connection = null;
List customerList = new ArrayList<>();
try {
String sql = "SELECT * FROM customer";
connection = DriverManager.getConnection(URL, USERNAME, PASSWORD);
//《1》 connection = DatabaseHelper.getConnection(); 此时还没写DatabaseHelper工具类。。。
PreparedStatement statement = connection.prepareStatement(sql);
ResultSet rs = statement.executeQuery();
while (rs.next()) {
Customer customer = new Customer();
customer.setId(rs.getLong("id"));
customer.setName(rs.getString("name"));
customer.setContact(rs.getString("contact"));
customer.setEmail(rs.getString("email"));
customer.setTelephone(rs.getString("telephone"));
customer.setRemark(rs.getString("remark"));
customerList.add(customer);
}
} catch (SQLException e) {
LOGGER.error("execute sql failure", e);
} finally {
if (connection != null) {
try {
connection.close();
}catch (SQLException e){
LOGGER.error("close connection failure", e);
}
}
// TODO: 2022/1/17
// DatabaseHelper.closeConnection(connection); 此时还没写DatabaseHelper工具类。。。
}
return customerList;
}
运行一下getCustomerList方法的单元测试,如果不出意外的话,应该是“绿条”,表示可以测试通过,相反,如果出现“红条”就表示测试失败,我们可以查看控制台以了解具体的错误原因。
虽然以上代码可以运行,基本的功能算是可以实现了,但问题还是非常多,具体包括以下两个方面:
- (1)在 CustomerService类中读取config.properties文件,这是不合理的,毕竟将来还有很多其他Service类需要做同样的事情,我们最好能将这些公共性的代码提取出来。
- (2)执行一条 select语句需要编写一大堆代码,而且还必须使用try..catch...finally结构,开发效率明显不高。
用什么方法来解决以上这些问题呢?
我们先来解决第一个问题。
创建一个org.smart4j.chapter2.helper包,在该包中创建一个 DatabaseHelper类,代码如下: