第一节 创建工程,引入依赖

1、架构

1.1、架构的概念

『架构』其实就是『项目的结构』,只是因为架构是一个更大的词,通常用来形容比较大规模事物的结构。

1.2、单一架构

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

  • 一个项目包含一个工程
  • 导出一个 war 包
  • 放在一个 Tomcat 上运行

2、创建工程

3、引入依赖

3.1、搜索依赖信息的网站

3.1.1、到哪儿找?

https://mvnrepository.com/

3.1.2、怎么选择?

  • 确定技术选型:确定我们项目中要使用哪些技术
  • 到 mvnrepository 网站搜索具体技术对应的具体依赖信息

  • 确定这个技术使用哪个版本的依赖

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

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

TIP

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

  • 项目本身所属行业的基本特点
  • 项目具体的功能需求
  • 项目预计访问压力程度
  • 项目预计将来需要扩展的功能
  • 设计项目总体的体系结构

3.2、持久化层所需依赖

  • mysql:mysql-connector-java:5.1.37
  • com.alibaba:druid:1.2.8
  • commons-dbutils:commons-dbutils:1.6

3.3、表述层所需依赖

  • javax.servlet:javax.servlet-api:3.1.0
  • org.thymeleaf:thymeleaf:3.0.11.RELEASE

3.4、辅助功能所需依赖

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

3.5、最终完整依赖信息

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

4、建包

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

第二节 搭建环境:持久化层

1、数据建模

1.1、物理建模

create database db_imperial_court;use db_imperial_court;create table t_emp
(emp_id         int primary key auto_increment,emp_name       char(100) not null,emp_position   char(100) not null,login_account  char(100) not null unique,login_password char(100) not null
);insert into t_emp(emp_name, emp_position, login_account, login_password)
values ('爱新觉罗·玄烨', 'emperor', 'xiaoxuanzi1654', '25325C896624D444B2E241807DCAC98B'), # 16540504('纳兰明珠', 'minister', 'brightball1635', 'A580D0EF93C22036C859E194C14CB777'),   # 16351119('赫舍里·索额图', 'minister', 'tutu1636', 'E40FD7D49B8B7EF46F47407D583C3538'); # 17030921create table t_memorials
(memorials_id          int primary key auto_increment,memorials_title       char(100)     not null,memorials_content     varchar(5000) not null,memorials_emp         int           not null,memorials_create_time char(100),feedback_time       char(100),feedback_content    varchar(1000),memorials_status      int           not null
);insert into t_memorials(memorials_title,memorials_content,memorials_emp,memorials_create_time,feedback_time,feedback_content,memorials_status)
values ('浙江巡抚奏钱塘堤决口疏', '皇上啊,不好啦!钱塘江发大水啦!堤坝冲毁啦!您看这咋弄啊!', 2, '1690-05-07', null, null, 0),('左都御史参鳌拜圈地疏', '皇上啊,鳌拜这厮不是东西呀!占老百姓的地哇!还打人呀!您看咋弄啊!', 3, '1690-04-14', null, null, 0),('都察院劾吴三桂不臣疏', '皇上啊,不得了啦!吴三桂那孙子想造反呀!', 2, '1693-11-18', null, null, 0),('兵部奏准噶尔犯境疏', '皇上啊,不得了啦!葛尔丹要打过来了呀!', 3, '1693-11-18', null, null, 0),('朝鲜使臣朝拜事宜呈皇上御览', '皇上啊!朝鲜国的人要来啦!咱们请他们吃猪肉炖粉条子吧!', 2, '1680-06-11', null, null, 0),('英吉利炮舰购买事宜疏', '皇上啊!英国的小船船咱们买多少啊?', 3, '1680-06-12', null, null, 0),('劾杭州织造贪墨疏', '皇上啊!杭州织造有问题啊!', 2, '1680-06-13', null, null, 0),('禀畅春园落成疏', '皇上啊!畅春园修好了哇!您啥时候过来看看呀!', 3, '1680-06-14', null, null, 0),('请旨木兰秋狝疏', '皇上啊!秋天到啦,又该打猎啦!', 2, '1680-06-15', null, null, 0),('核准西北军饷银两疏', '皇上啊!您看看这钱数算的对不对呀!', 3, '1680-06-16', null, null, 0),('请旨裁撤三藩疏', '皇上啊!咱们不裁撤三藩就芭比Q了哇!', 2, '1680-06-17', null, null, 0),('蒙古王公进京朝拜疏', '皇上啊!蒙古王公要来啦!咱们请他们吃猪肉炖粉条子吧!', 3, '1680-06-18', null, null, 0),('礼部请旨九阿哥赐名疏', '皇上啊!您看九阿哥该叫什么名字呀?', 2, '1680-06-19', null, null, 0),('户部尚书请旨告老还乡疏', '皇上啊!臣想回家养老啦!您看看啥时候给臣把俸禄结一下啊!', 3, '1680-06-20', null, null, 0),('查江宁织造贪墨疏', '皇上啊!江宁织造有问题啊!', 2, '1680-06-21', null, null, 0);

1.2、逻辑建模

1.2.1、Emp 实体类

public class Emp {private Integer empId;private String empName;private String empPosition;private String loginAccount;private String loginPassword;

1.2.2、Memorials 实体类

public class Memorials {private Integer memorialsId;private String memorialsTitle;private String memorialsContent;// 奏折摘要数据库没有,这里是为了配和页面显示private String memorialsContentDigest;private Integer memorialsEmp;// 员工姓名数据库没有,这里是为了配合页面显示private String memorialsEmpName;private String memorialsCreateTime;private String feedbackTime;private String feedbackContent;private Integer memorialsStatus;

2、数据库连接信息

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

driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://192.168.198.100:3306/db_imperial_court
username=root
password=atguigu
initialSize=10
maxActive=20
maxWait=10000

3、获取数据库连接

3.1、创建 JDBCUtils 工具类

3.2、创建 javax.sql.DataSource 对象

// 将数据源对象设置为静态属性,保证大对象的单一实例
private static DataSource dataSource;static {// 1.创建一个用于存储外部属性文件信息的Properties对象Properties properties = new Properties();// 2.使用当前类的类加载器加载外部属性文件:jdbc.propertiesInputStream inputStream = JDBCUtils.class.getClassLoader().getResourceAsStream("jdbc.properties");try {// 3.将外部属性文件jdbc.properties中的数据加载到properties对象中properties.load(inputStream);// 4.创建数据源对象dataSource = DruidDataSourceFactory.createDataSource(properties);} catch (Exception e) {e.printStackTrace();}}

3.3、创建 ThreadLocal 对象

3.3.1、提出需求

3.3.1.1、在一个方法内控制事务

如果在每一个 Service 方法中都写下面代码,那么代码重复性就太高了:

try{// 1、获取数据库连接// 重要:要保证参与事务的多个数据库操作(SQL 语句)使用的是同一个数据库连接Connection conn = JDBCUtils.getConnection();// 2、核心操作// ...// 3、核心操作成功结束,可以提交事务conn.commit();}catch(Exception e){// 4、核心操作抛出异常,必须回滚事务conn.rollBack();}finally{// 5、释放数据库连接JDBCUtils.releaseConnection(conn);}
3.3.1.2、将重复代码抽取到 Filter

所谓『当前请求覆盖的 Servlet 方法、Service 方法、Dao 方法』其实就是 chain.doFilter(request, response) 间接调用的方法。

public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain){try{// 1、获取数据库连接// 重要:要保证参与事务的多个数据库操作(SQL 语句)使用的是同一个数据库连接Connection conn = JDBCUtils.getConnection();// 重要操作:关闭自动提交功能connection.setAutoCommit(false);// 2、核心操作:通过 chain 对象放行当前请求// 这样就可以保证当前请求覆盖的 Servlet 方法、Service 方法、Dao 方法都在同一个事务中。// 同时各个请求都经过这个 Filter,所以当前事务控制的代码在这里只写一遍就行了,// 避免了代码的冗余。chain.doFilter(request, response);// 3、核心操作成功结束,可以提交事务conn.commit();}catch(Exception e){// 4、核心操作抛出异常,必须回滚事务conn.rollBack();}finally{// 5、释放数据库连接JDBCUtils.releaseConnection(conn);}}
3.3.1.3、数据的跨方法传递

通过 JDBCUtils 工具类获取到的 Connection 对象需要传递给 Dao 方法,让事务涉及到的所有 Dao 方法用的都是同一个 Connection 对象。

但是 Connection 对象无法通过 chain.doFilter() 方法以参数的形式传递过去。

所以从获取到 Connection 对象到使用 Connection 对象中间隔着很多不是我们自己声明的方法——我们无法决定它们的参数。

3.2.2、ThreadLocal 对象的功能

  • 全类名:java.lang.ThreadLocal
  • 泛型 T:要绑定到当前线程的数据的类型
  • 具体三个主要的方法:
方法名 功能
set(T value) 将数据绑定到当前线程
get() 从当前线程获取已绑定的数据
remove() 将数据从当前线程移除

3.2.3、Java 代码

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

3.4、声明方法:获取数据库连接

/*** 工具方法:获取数据库连接并返回* @return*/
public static Connection getConnection() {Connection connection = null;try {// 1、尝试从当前线程检查是否存在已经绑定的 Connection 对象connection = threadLocal.get();// 2、检查 Connection 对象是否为 nullif (connection == null) {// 3、如果为 null,则从数据源获取数据库连接connection = dataSource.getConnection();// 4、获取到数据库连接后绑定到当前线程threadLocal.set(connection);}} catch (SQLException e) {e.printStackTrace();// 为了调用工具方法方便,编译时异常不往外抛// 为了不掩盖问题,捕获到的编译时异常封装为运行时异常抛出throw new RuntimeException(e);}return connection;
}

3.5、声明方法:释放数据库连接

/*** 释放数据库连接*/
public static void releaseConnection(Connection connection) {if (connection != null) {try {// 在数据库连接池中将当前连接对象标记为空闲connection.close();// 将当前数据库连接从当前线程上移除threadLocal.remove();} catch (SQLException e) {e.printStackTrace();throw new RuntimeException(e);}}}

3.6、初步测试

public class ImperialCourtTest {@Testpublic void testGetConnection() {Connection connection = JDBCUtils.getConnection();System.out.println("connection = " + connection);JDBCUtils.releaseConnection(connection);}}

3.7、完整代码

/*** 功能1:从数据源获取数据库连接* 功能2:从数据库获取到数据库连接后,绑定到本地线程(借助 ThreadLocal)* 功能3:释放线程时和本地线程解除绑定*/
public class JDBCUtils {// 数据源成员变量设置为静态资源,保证大对象的单例性;同时保证静态方法中可以访问private static DataSource dataSource;// 由于 ThreadLocal 对象需要作为绑定数据时 k-v 对中的 key,所以要保证唯一性// 加 static 声明为静态资源即可保证唯一性private static ThreadLocal<Connection> threadLocal = new ThreadLocal<>();// 在静态代码块中初始化数据源static {try {// 操作思路分析:// 从 jdbc.properties 文件中读取连接数据库的信息// 为了保证程序代码的可移植性,需要基于一个确定的基准来读取这个文件// 确定的基准:类路径的根目录。resources 目录下的内容经过构建操作中的打包操作后会确定放在 WEB-INF/classes 目录下。// WEB-INF/classes 目录存放编译好的 *.class 字节码文件,所以这个目录我们就称之为类路径。// 类路径无论在本地运行还是在服务器端运行都是一个确定的基准。// 操作具体代码:// 1、获取当前类的类加载器ClassLoader classLoader = JDBCUtils.class.getClassLoader();// 2、通过类加载器对象从类路径根目录下读取文件InputStream stream = classLoader.getResourceAsStream("jdbc.properties");// 3、使用 Properties 类封装属性文件中的数据Properties properties = new Properties();properties.load(stream);// 4、根据 Properties 对象(已封装了数据库连接信息)来创建数据源对象dataSource = DruidDataSourceFactory.createDataSource(properties);} catch (Exception e) {e.printStackTrace();// 为了避免在真正抛出异常后,catch 块捕获到异常从而掩盖问题,// 这里将所捕获到的异常封装为运行时异常继续抛出throw new RuntimeException(e);}}/*** 工具方法:获取数据库连接并返回* @return*/public static Connection getConnection() {Connection connection = null;try {// 1、尝试从当前线程检查是否存在已经绑定的 Connection 对象connection = threadLocal.get();// 2、检查 Connection 对象是否为 nullif (connection == null) {// 3、如果为 null,则从数据源获取数据库连接connection = dataSource.getConnection();// 4、获取到数据库连接后绑定到当前线程threadLocal.set(connection);}} catch (SQLException e) {e.printStackTrace();// 为了调用工具方法方便,编译时异常不往外抛// 为了不掩盖问题,捕获到的编译时异常封装为运行时异常抛出throw new RuntimeException(e);}return connection;}/*** 释放数据库连接*/public static void releaseConnection(Connection connection) {if (connection != null) {try {// 在数据库连接池中将当前连接对象标记为空闲connection.close();// 将当前数据库连接从当前线程上移除threadLocal.remove();} catch (SQLException e) {e.printStackTrace();throw new RuntimeException(e);}}}}

4、BaseDao

4.1、泛型的说明

4.2、创建 QueryRunner 对象

// DBUtils 工具包提供的数据库操作对象
private QueryRunner runner = new QueryRunner();

4.3、通用增删改方法

特别说明:在 BaseDao 方法中获取数据库连接但是不做释放,因为我们要在控制事务的 Filter 中统一释放。

/*** 通用的增删改方法,insert、delete、update 操作都可以用这个方法* @param sql 执行操作的 SQL 语句* @param parameters SQL 语句的参数* @return 受影响的行数*/
public int update(String sql, Object ... parameters) {try {Connection connection = JDBCUtils.getConnection();int affectedRowNumbers = runner.update(connection, sql, parameters);return affectedRowNumbers;} catch (SQLException e) {e.printStackTrace();// 如果真的抛出异常,则将编译时异常封装为运行时异常抛出new RuntimeException(e);return 0;}}

4.4、查询单个对象

/*** 查询单个对象* @param sql 执行查询的 SQL 语句* @param entityClass 实体类对应的 Class 对象* @param parameters 传给 SQL 语句的参数* @return 查询到的实体类对象*/
public T getSingleBean(String sql, Class<T> entityClass, Object ... parameters) {try {// 获取数据库连接Connection connection = JDBCUtils.getConnection();return runner.query(connection, sql, new BeanHandler<>(entityClass), parameters);} catch (SQLException e) {e.printStackTrace();// 如果真的抛出异常,则将编译时异常封装为运行时异常抛出new RuntimeException(e);}return null;
}

4.5、查询多个对象

/*** 查询返回多个对象的方法* @param sql 执行查询操作的 SQL 语句* @param entityClass 实体类的 Class 对象* @param parameters SQL 语句的参数* @return 查询结果*/
public List<T> getBeanList(String sql, Class<T> entityClass, Object ... parameters) {try {// 获取数据库连接Connection connection = JDBCUtils.getConnection();return runner.query(connection, sql, new BeanListHandler<>(entityClass), parameters);} catch (SQLException e) {e.printStackTrace();// 如果真的抛出异常,则将编译时异常封装为运行时异常抛出new RuntimeException(e);}return null;
}

4.6、测试

private BaseDao<Emp> baseDao = new BaseDao<>();@Test
public void testGetSingleBean() {String sql = "select emp_id empId,emp_name empName,emp_position empPosition,login_account loginAccount,login_password loginPassword from t_emp where emp_id=?";Emp emp = baseDao.getSingleBean(sql, Emp.class, 1);System.out.println("emp = " + emp);}@Test
public void testGetBeanList() {String sql = "select emp_id empId,emp_name empName,emp_position empPosition,login_account loginAccount,login_password loginPassword from t_emp";List<Emp> empList = baseDao.getBeanList(sql, Emp.class);for (Emp emp : empList) {System.out.println("emp = " + emp);}}@Test
public void testUpdate() {String sql = "update t_emp set emp_position=? where emp_id=?";String empPosition = "minister";String empId = "3";int affectedRowNumber = baseDao.update(sql, empPosition, empId);System.out.println("affectedRowNumber = " + affectedRowNumber);}

5、子类 Dao

创建接口和实现类如下:

本文章参考B站 尚硅谷2022版Maven教程(maven入门+高深,全网无出其右!),仅供个人学习使用,部分内容为本人自己见解,与尚硅谷无关。

【Maven基础】单一架构案例(一)相关推荐

  1. 2022版Maven教程 - 第六章 单一架构案例

    2022版Maven教程 - 第六章 单一架构案例 一.创建工程,引入依赖 1.架构 ①架构的概念 ②单一架构 2.创建工程 3.引入依赖 ①搜索依赖信息的网站 [1]到哪儿找? [2]怎么选择? ② ...

  2. 视频教程-零基础Redis详细案例讲解课程(第3季)---键管理、慢查询、管理命令-NoSQL

    零基础Redis详细案例讲解课程(第3季)---键管理.慢查询.管理命令 十年IT经验工作经验,现任系统架构师职务,自创易学笔记序列,擅长培养新人学习能力和学习思维,授人以鱼不如授人以渔.易学笔记全栈 ...

  3. 微服务架构实战项目Serverless的持续交付与架构案例

    Serverless的持续交付 架构图如下: 在这个架构中,我们采用了前后端分离的技术.我们把 HTML,JS, CSS 等静态内容部署在 S3 上,并通过 CloudFront 作为 CDN 构成了 ...

  4. 架构评审,技术总监怒了,丢给我 400 个架构案例,先研究透!

    最近,我看到一个群里关于架构师的讨论: "架构师很'高大上'啊,平常画画架构图,对我等程序员指指点点不就可以了." "感觉我们公司架构师没啥用啊,可有可无吧." ...

  5. 图书推荐:《ASP.NET.基础教程——C#案例版》

    本书结合用C#语言编写的可实际运行的示例代码,讨论了ASP.NET的构架.Web窗体.配置.HTTP管道.故障诊断和错误处理.验证.数据绑定.自定义控件.缓存.状态管理和安全性,阐述用C#构建基于We ...

  6. 大型网站电商网站架构案例和技术架构的示例

    大型网站架构是一个系列文档,欢迎大家关注.本次分享主题:电商网站架构案例.从电商网站的需求,到单机架构,逐步演变为常用的,可供参考的分布式架构的原型.除具备功能需求外,还具备一定的高性能,高可用,可伸 ...

  7. 微服务架构案例(01):项目技术选型简介,架构图解说明

    本文源码:GitHub·点这里 || GitEE·点这里 一.单体架构 单体架构在中等偏小的业务中比较常见,场景模式就是单个应用.单个数据库.一个程序包(例如war格式或者Jar格式)包含所有业务需求 ...

  8. 物联网大数据平台软件开发架构案例解析

    物联网大数据平台软件开发架构案例解析 有人说物联网是引领信息技术的第三次浪潮. 第一次浪潮是个人电脑的出现,开创了信息时代的第一次革命,此次浪潮成就了微软.IBM等巨头. 第二次浪潮是以信息传输为特征 ...

  9. Serverless 风格微服务的持续交付(上):架构案例

    GitChat 作者:顾宇 原文:Serverless 风格微服务的持续交付(上):架构案例 关注微信公众号:GitChat 技术杂谈 ,一本正经的讲技术 [不要错过文末彩蛋] 无服务器架构 (Ser ...

最新文章

  1. 张海腾:语音识别实践教程
  2. 个人博客代码_这四个博客模板美观大气,做个漂亮的个人博客必须得收藏
  3. java中map可以为空吗_Java: Map里面的键和值可以为空吗?
  4. redhat和ubuntu上部署本地源
  5. 魅影远程监控(企业版)
  6. 缓存雪崩、击穿、穿透解决方案
  7. 自由职业者:5步拿下新项目
  8. 基于JAVA+SpringMVC+Mybatis+MYSQL的大学生考勤管理系统及智能分析系统
  9. 大学计算机二级的试题,大学计算机二级考试试题及其答案_..doc
  10. DBeaver连接mysql驱动下载失败怎么办?
  11. 加壳后软件报毒解决办法
  12. 迅雷手机版苹果版_免费下载迅雷 iOS 版
  13. powerbi导入地图_PowerBI地图可视化的那些套路
  14. 熊出没之伐木机器人_盘点熊出没中李老板的五大伐木工,光头强无人能代替
  15. KK 在 68 岁生日时给出的 68 条建议[翻译]#yyds干货盘点#
  16. Kruise Rollout v0.2.0 版本发布:支持 Gateway API、StatefulSet 分批发布等能力
  17. channel的用法
  18. 最全面的文本生成评价指标大盘点
  19. 系列教程--Linux基础--12--用户和组
  20. 解决NameError: name ‘xxx‘ is not defined

热门文章

  1. 基金可视化分析,帮你做基金选择
  2. Scarborough Fair(斯卡布罗集市)
  3. 华为数通笔记-BGP路由选路实验
  4. virtualBox下配置已经安装好的ubuntu内存大小
  5. 程序员养生 需从心态、饮食、健身三方面入手
  6. ASP.NET编程规范
  7. 工作流应用场景简单介绍
  8. 过年整点花活:Python修仙等级测试【限时版】~ 邻居家的小屁孩可以拿来测试一下......
  9. Python快速排序算法
  10. 循环 和 try<-except 用法