# 架构

架构的概念

[架构] 其实就是 [项目的 < font color='red'> 结构 </font>], 只是因为结构是一个更大的词,通常用来形容比较大规模事物的结构

单一架构

单一架构也叫 [all-in-one] 结构,就是所有代码,配置文件,各种资源都在同一个工程

  • 一个项目包含一个工程

  • 导出一个 war 包

  • 放在一个 Tomcat 上运行

# 创建工程

image-20230113220548630

# 引入依赖

1 搜索依赖信息的网站

到哪找?

maven

怎么选择?

  • 确定技术选型:确定我们项目中要使用那些技术

  • 到 mvnrepository 网站搜索具体技术对应的具体依赖信息

image_2023-01-13-21-17-13

  • 确定使用哪个版本的依赖
    • 考虑因素 1: 看是否有别的技术要求这里必须用某一个版本
    • 考虑因素 2: 如果没有硬性要求,那么选择较高版本或下载量大的版本

image_2023-01-13-21-24-46

image_2023-01-13-21-25-08

  • 在实际使用中检验所有依赖信息是否都正常可用

确定技术选型,组建依赖列表,项目划分模块...等等这些操作其实都是属于架构设计的范畴

  • 项目本身所属行业的基本特点

  • 项目具体的功能需求

  • 项目预计访问压力程度

2 持久化层所需依赖

  • mysql:mysql-connector-java:5.1.37

  • com.alibaba:druid:1.2.8

  • commons-dbutils:commons-dbutils:1.6

3 表述层所需依赖

  • javax.servlet:javax.servlet-api:3.1.0

  • org.thymeleaf:thymeleaf:3.0.11.RELEASE

4 辅助功能所需功能

  • junit:junit:4.12

  • ch.qos.logback:logback-classic:1.2.3

最终完整依赖信息

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.37</version>
</dependency>
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.2.8</version>
</dependency>
<dependency>
    <groupId>commons-dbutils</groupId>
    <artifactId>commons-dbutils</artifactId>
    <version>1.6</version>
</dependency>
<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>javax.servlet-api</artifactId>
    <version>3.1.0</version>
    <scope>provided</scope>
</dependency>
<dependency>
    <groupId>org.thymeleaf</groupId>
    <artifactId>thymeleaf</artifactId>
    <version>3.0.11.RELEASE</version>
</dependency>
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
    <version>1.2.3</version>
    <scope>test</scope>
</dependency>

# 建包

package 功能package 名称
主包com.atguigu.imperial.court
子包 [实体类]com.atguigu.imperial.court.entiy
子包 [Servlet 基类包]com.arguigu.imperial.court.servlet.base
子包 [Servlet 模块包]com.atguigu.imperial.court.servlet.module
子包 [Servlet 接口包]com.arguigu.imperial.court.service.api
子包 [Service 实现类包]com.arguigu.imperial.court.service.impl
子包 [Dao 接口包]com.atguigu.imperial.court.dao.api
子包 [Dao 实现类包]com.atguigu.imperial.court.impl
子包 [Filter]com.atguigu.imperial.court.filter
子包 [异常类包]com.atguigu.imperial.court.execption
子包 [工具类]com.atguigu.imperial.court.tuil
子包 [测试类]com.atguidu.imperial.court.test

# 数据库连接信息

导入配置文件 - 位置:main 目录下的 resources 中

说明:这是我们第一次用到 Maven 约定目录结构中的 resources 目录,这个目录存放各种配置文件

image-20230114080140977

ThreadLocal 对象的功能

  • 分析图

image_2023-01-14-10-59-20

  • 全类名:java.lang.ThreadLocal<T>

  • 泛型 T: 要绑定到当前线程的数据的类型

  • 具体三个主要的方法:

方法名功能
set(T value)将数据绑定到当前线程
get()从当前线程获取已绑定的数据
remove()将数据从当前线程移除

java 代码

// 由于 ThreadLocal 对象需要作为绑定数据时 & lt;k-v > 对中的 Key, 所以要保证唯一性
// 加 static 声明为静态资源即可保证唯一性
private static ThreadLocal<connection> threadlocal = new ThreadLocal<>();

# 获取数据库连接

private static DataSource druid;
private static ThreadLocal<Connection> threadlocal = new ThreadLocal<>();
static{
    try{
        // 为了保证程序代码的可移植性,需要基于一个基准来读取这个文件
        InputStream input = JDBCUtils.class.getClassLoader().getResourceAsStream("druid.properties");
        Properties pro = new Properties();
        pro.load(input);
        druid = DruidDataSourceFactory.createDataSource(pro);
    }catch(Exception e){
        throw new RuntimeException(e);
    }
}
public static Connection getConnection(){
    Connection connection = null;
    try{
        //1. 尝试从当前线程检查是否存在已经绑定的 Connection 对象
        connection = threadlocal.get();
        //2. 检查 connection 对象是否为 Null
        if(connection == null){
            //3. 如果为 null, 则从数据资源获取数据连接
            connection = druid.getConnection();
            //4. 获取到数据库连接绑定到当前线程
            threadlocal.set(connection);
        }
    }catch(Exception e){
        throw new RuntimeException(e);
    }
    return connection;
}

# 释放数据库连接

public void releaseclose(Connection connection){
   try{
       if(connection != null){
           // 关闭连接,释放资源
           connection.close();
           p("数据库已关闭");
           // 将当前数据库连接从线程上删除
           threadlocal.remove();
       }
   }catch(Exception e){
       throw new RuntimeException(e);
   }
}

# 初步测试

@Test
public void test(){
    JDBCUtils j = new JDBCUtils();
    Connection c = j.getConnection();
    //System.out.println();
    p("测试结果");
    //System.out.println();
    p(c);
}

image_2023-01-14-13-29-21

  • JDBCUtils 完整代码

点击查看代码块

# BaseDao

1 泛型的说明

image_2023-01-14-22-37-20

2 创建 QueryRunner 对象

//DBUtils 工具类提供的对象用于操作数据库
private static QueryRunner queryrunner = new QueryRunner();

3 通用增删改方法

<font style="color:red"> 特别说明:</font > 在 BaseDao 方法中获取数据库连接但是不做释放,因为我们要在控制事务的 Filter 中统一释放

/**
 * 通用的增删改方法,insert,delete,update
 * @param sql 执行操作的 sql 语句
 * @param parameter sql 语句的参数
 */
public void update(String sql,Object...parameter){
    Connection connection = JDBCUtils.getConnection();
    try{
        int i = queryrunner.update(connection,sql,parameter);
        if(i > 0){
            p("执行成功!");
        }else{
            p("show");
            p("执行失败!!!");
        }
    }catch(Exception e){
        new RuntimeException(e);
    }
}

4 查询单个对象

/**
 * 查询单个对象
 * @param sql 执行查询的 sql 语句
 * @param clazz 实体类对应的 class 对象
 * @param parameter 传递给 sql 语句的参数
 * @return 查询到的实体类对象
 */
public T getSingleBean(String sql,Class<T> clazz,Object... parameter){
    Connection connection = null;
    try{
        connection = JDBCUtils.getConnection();
        return queryrunner.query(connection,sql,new BeanHandler<T>(clazz),parameter);
    }catch(Exception e){
        // 如果真的抛出异常,则将编译时异常封装为运行时异常抛出
        new RuntimeException(e);
    }
    return null;
}

5 查询多个对象

/**
 * 查询返回多个对象的方法
 * @param sql 执行查询操作的 sql 语句
 * @param clazz 实体类的 class 对象
 * @param parameter sql 语句的参数
 * @return 查询结果
 */
public List<T> getBeanList(String sql,Class<T> clazz,Object...parameter){
    try{
        Connection connection = JDBCUtils.getConnection();
        return queryrunner.query(connection,sql,new BeanListHandler<T>(clazz));
    }catch(Exception e){
        new RuntimeException(e);
    }
    return null;
}

测试

private static BaseDao<Emp> dao = new BaseDao();
    @Test
    public void test1(){// 如果数据库字段名与 Emp 类中的字段名不一致那么就会获取不到值我们可以使用别名来纠正获取到数据库的值
        String sql = "select emp_id empId,emp_name,emp_position,login_account,login_password from t_emp where emp_id = ?";
        Emp singleBean = dao.getSingleBean(sql, Emp.class,2);
        p(singleBean);
    }
    @Test
    public void test2(){// 如果数据库字段名与 Emp 类中的字段名不一致那么就会获取不到值我们可以使用别名来纠正获取到数据库的值
        String sql = "select emp_id empId,emp_name,emp_position,login_account,login_password from t_emp";
        List<Emp> beanList = dao.getBeanList(sql, Emp.class);
        if(beanList == null){
            p("空指针啦!!!");
        }else{
            for(Emp i:beanList){
                p(i);
            }
        }
    }
    @Test
    public void test3(){
        String sql = "update t_emp set emp_position = ? where emp_id = ?";
        dao.update(sql,"qwerty",1);
    }
    @Test
    public void test(){
        JDBCUtils j = new JDBCUtils();
        Connection c = j.getConnection();
        //System.out.println();
        p("测试结果");
        //System.out.println();
        p(c);
    }

# 子类 Dao

创建接口和实现如下:

image_2023-01-15-10-33-29

# 事务层

# TransactionFilter

1 创建 Filter 类

image_2023-01-15-11-33-39

2 TransactionFilter 完整代码

public class TransactionFilter implements Filter {
    private static Set<String> staticResourceExtNameSet;
    static{
        staticResourceExtNameSet = new HashSet<>();
        staticResourceExtNameSet.add(".png");
        staticResourceExtNameSet.add(".jpg");
        staticResourceExtNameSet.add(".css");
        staticResourceExtNameSet.add(".js");
    }
    /**
     * 项目每次被访问都会执行
     * @param servletRequest
     * @param servletResponse
     * @param filterChain
     * @throws IOException
     * @throws ServletException
     */
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
//        前置操作:排除静态资源
        HttpServletRequest req = (HttpServletRequest) servletRequest;
        String servletPath = req.getServletPath();
        String extName = servletPath.substring(servletPath.lastIndexOf("."));
        if(staticResourceExtNameSet.contains(extName)){
//            如果检测到当前请求确实是静态资源,则直接放行,不做事务操作
            filterChain.doFilter(servletRequest,servletResponse);
//            当前方法停止执行
            return;
        }
        Connection connection = null;
        try{
//            1 获取数据库连接
            connection = JDBCUtils.getConnection();
//            设置为手动提交
            connection.setAutoCommit(false);
//            2 核心操作
            filterChain.doFilter(servletRequest,servletResponse);
//            3 提交事务
            connection.commit();
        }catch(Exception e){
//            4 事务回滚
            try{
                connection.rollback();
            }catch(Exception e1){
                e1.printStackTrace();
            }
//            页面显示:将这里捕获到的异常发送到指定页面显示
//            获取异常信息
            String message = e.getMessage();
//            将异常信息存入请求域
            req.setAttribute("message",message);
//            将请求转发到指定页面
            servletRequest.getRequestDispatcher("/").forward(req,servletResponse);
        }finally{
//            5 释放连接
            JDBCUtils.releaseclose(connection);
        }
    }
    /**
     * 当项目结束后执行
     */
    @Override
    public void destroy() {
    }
    /**
     * 在项目开启或者开启后执行一次
     * @param filterConfig
     * @throws ServletException
     */
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    }
}

3 配置 web.xml

<font style="color:red"> 注意: </font> 需要将当前工程改为 web 工程

image_2023-01-15-12-33-21

3.1 配置 Filter

<filter>
    <filter-name>filter</filter-name>
    <filter-class>com.dkx.maven.aourt.filter.TransactionFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>filter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

4 注意点

<font style="color='red'"> 确保异常回滚 </font>

在程序执行的过程中,必须让所有 catch 块都把编译时异常转换为运行时异常抛出;如果不这么做,在 TransactionFilter 中 catch 就无法捕获到底层抛出的异常,那么该回滚的时候就无法回滚

  1. 确保异常回滚

由于诸多操作都是在使用同一个数据库连接,那么中间任何一个环节释放数据库连接都会导致后续操作无法正常完成

  1. 慎防数据库连接提前释放

# 表述层

# 视图模板技术 Thymeleaf

1 服务器端渲染

image_2023-01-15-13-32-42

2 Thymeleaf 简要工作机制

  1. 初始化阶段
  • 目标:创建 TemplateEngine 对象

  • 封装:因为对每一个请求来说,TemplateEngine 对象使用的都是同一个,所以在初始化阶段准备好

image_2023-01-15-15-52-36

3 逻辑视图与物理视图

4 Thymeleaf 的页面语法

  1. 请求处理阶段

image_2023-01-15-18-08-51

3 逻辑视图与物理视图

假设有下列页面地址

/WEB-INF/pages/apple.html<br>/WEB-INF/pages/banana.html<br>/WEB-INF/pages/orange.html<br>/WEB-INF/pages/grape.html<br>/WEB-INF/pages/egg.html

这样的地址可以直接访问到页面,我们称之为:物理视图,而将物理视图中前面,后面的固定内容抽取出来,让每次请求指定中间变化部分即可,那么中间变化部分就叫:逻辑视图

image_2023-01-16-10-50-45

4 ViewBaseServlet 完整代码

为了简化视图页面处理过程,我们将 Thymeleaf 模板引擎的初始化和请求处理过程封装到一个 Servlet 基类中: ViewBaseServlet 以后负责具体模块业务功能的 Servley 继承该基类即可直接使用

<font style="'blue'"> 特别提醒:</font > 这个类 < font style="'blue'"> 不需要掌握 </font>, 因为以后都被框架封装了,我们现在只是暂时用一下

image_2023-01-16-10-56-18

5 声明初始化参数

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">
    <!-- 配置 web 引用初始化参数指定视图前缀,后缀 -->
    <!--
        物理视图举例:/WEB-INF/pages/index.html
        对应逻辑视图:index
    -->
    <context-param>
        <param-name>view-prefix</param-name>
        <param-value>/WEB-INF/pages</param-value>
    </context-param>
    <context-param>
        <param-name>view-suffix</param-name>
        <param-value>.html</param-value>
    </context-param>
    <filter>
        <filter-name>filter</filter-name>
        <filter-class>com.dkx.maven.aourt.filter.TransactionFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>filter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
</web-app>

image_2023-01-16-18-36-53

# ModuleBaseServlet

1 提出问题

[1] 我们的需求

image_2023-01-16-18-39-15

[2] HttpServlet 的局限

  • doGet () 方法:处理 GET 请求

  • doPost () 方法:处理 POST 请求

2 解决方案

  • 每个请求附带一个 <font style="color:red"> 请求参数 </font>, 表明自己要调用的目标方法

  • Servlet 根据目标方法名通过 <font style="color:red"> 反射 </font > 调用自己目标方法