《架构探险--从零开始写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
                    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);
        }
    }
    
    1. 继承HttpServlet,让它成为servlet类
    2. 覆盖父类的doGet方法,用于接收GET请求
    3. 在doGet方法中数据,将其放到HttpServletRequest对象然后转发到/WEB-INF/jsp/hello.jsp
    4. 使用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 master

2、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
                    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类,代码如下:

5、创建一个 DatabaseHelper类(接上文)

todo 只复制到55页