当前位置: 首页 > news >正文

Java设计模式:四、行为型模式-04:中介者模式

文章目录

  • 一、定义:中介者模式
  • 二、模拟场景:中介者模式
  • 三、违背方案:中介者模式
    • 3.1 工程结构
    • 3.2 创建数据库
    • 3.3 JDBC工具类
    • 3.4 单元测试
  • 四、改善代码:中介者模式
    • 4.1 工程结构
    • 4.2 中介者工程结构图
    • 4.3 资源和配置类
      • 4.3.1 XML配置对象
      • 4.3.2 资源工具类
      • 4.3.3 配置类
    • 4.4 SqlSession实现
      • 4.4.1 定义SqlSession接口
      • 4.4.2 SqlSession具体实现类
      • 4.4.3 定义 SqlSession 工厂接口
      • 4.4.4 SqlSessionFactory工厂接口具体实现类
      • 4.4.5 SqlSessionFactoryBuilder 建造者工厂实现
    • 4.5 数据库对象类和持久层
      • 4.5.1 用户类
      • 4.5.2 学校类
      • 4.5.3 用户持久层接口
      • 4.5.4 学校持久层接口
      • 4.5.5 用户持久层配置文件
      • 4.5.6 学校持久层配置文件
    • 4.6 单元测试
      • 4.6.1 配置文件
      • 4.6.2 单条数据查询单元测试
      • 4.6.3 集合数据查询单元测试
  • 五、总结:中介者模式

一、定义:中介者模式

在这里插入图片描述

  • 中介者模式:当复杂功能应用之间重复调用,在中间添加一层中介者包装服务,对外提供简单、通用和易扩展的服务能力。
  • 中介者模式的使用场景:
    • 十字路口有交警指挥交通。
    • 飞机降落时有营运人员在搭台喊话。
    • 无论哪个方向来的候车都从站台上下。
    • 公司系统中有中台系统包装所有接口和提供统一的服务。
    • 平时用到的一些中间件,他们包装了底层的多种数据库的差异化,对外提供非常简单的调用

二、模拟场景:中介者模式

在这里插入图片描述

  • 模拟 MyBatis 手写 ORM 框架,通过操作数据库学习中介者模式。
  • 除了中间件层使用场景,对于一些外部接口,例如 N 中奖品服务,也可以由中台系统统一包装,再对外提供服务能力。
    • 这也是一种中介者模式思想方案的落地体现。
  • 本案例将对 JDBC 层包装,让用户在使用数据库服务时像使用 MyBatis 一样简单方便,通过对 ORM 框架源码技术迁移运用的方式学习中介者模式,更能增强和扩展知识栈。

三、违背方案:中介者模式

3.1 工程结构

design-17.0-1
|——src|——main|--java|--com.lino.design|--JDBCUtil.java

3.2 创建数据库

design.sql

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;-- ----------------------------
-- Table structure for school
-- ----------------------------
DROP TABLE IF EXISTS `school`;
CREATE TABLE `school`  (`id` bigint(20) NOT NULL COMMENT '主键',`name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '名称',`address` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '地址',`createTime` datetime NULL DEFAULT NULL COMMENT '创建时间',`updateTime` datetime NULL DEFAULT NULL COMMENT '更新时间',PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;-- ----------------------------
-- Records of school
-- ------------------------------ ----------------------------
-- Table structure for user
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user`  (`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',`name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '姓名',`age` int(11) NULL DEFAULT NULL COMMENT '年龄',`createTime` datetime NULL DEFAULT NULL COMMENT '创建时间',`updateTime` datetime NULL DEFAULT NULL COMMENT '更新时间',PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;-- ----------------------------
-- Records of user
-- ----------------------------
INSERT INTO `user` VALUES (1, '张三', 18, '2023-01-31 16:52:03', '2023-01-31 16:52:06');
INSERT INTO `user` VALUES (2, '李四', 18, '2023-01-31 16:52:16', '2023-01-31 16:52:18');SET FOREIGN_KEY_CHECKS = 1;

3.3 JDBC工具类

JDBCUtil.java

package com.lino.design;import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;/*** @description: JDBC工具类*/
public class JDBCUtil {private static Logger logger = LoggerFactory.getLogger(JDBCUtil.class);public static final String URL = "jdbc:mysql://127.0.0.1:3306/design";public static final String USER = "root";public static final String PASSWORD = "123456";public static void main(String[] args) throws Exception {// 1.加载驱动程序Class.forName("com.mysql.jdbc.Driver");// 2.获得数据库连接Connection conn = DriverManager.getConnection(URL, USER, PASSWORD);// 3.操作数据库,实现增删改查Statement stmt = conn.createStatement();ResultSet resultSet = stmt.executeQuery("SELECT id, name, age, createTime, updateTime FROM user");// 4.如果有数据,rs.next()返回truewhile (resultSet.next()) {logger.info("测试结果:姓名:{} 年龄:{}", resultSet.getString("name"), resultSet.getInt("age"));}}
}
  • 直接使用 JDBC 方式直接操作数据库,整个过程分为:加载驱动程序、获得数据库连接、操作数据库和获取执行结果。

3.4 单元测试

测试结果

21:46:53.615 [main] INFO  com.lino.design.JDBCUtil - 测试结果:姓名:张三 年龄:30
21:46:53.619 [main] INFO  com.lino.design.JDBCUtil - 测试结果:姓名:李四 年龄:18
  • 从测试结果可以看到,已经查询到了数据库中的数据。
  • 但如果全部的业务开发都这样实现,会非常麻烦。实际的开发中,会使用相应的框架,比如 MyBatisIBatisHibernate 等。

四、改善代码:中介者模式

  • 使用中介者模式模仿 MyBatisORM 框架的开发。
    • MyBatis 的源码涉及内容较多,虽然在使用时非常方便,直接使用注解或者 XML 配置就可以操作数据库返回结果。
    • 但在实现上,MyBatis 作为中间层已经处理了 SQL 语句的获取、数据库连接、执行和返回封装结果等。
  • 接下来把 MyBatis 最核心的部分抽离出来,手动实现一个 ORM 框架。

4.1 工程结构

design-17.0-2
|——src|——main|--java|--com.lino.design|--dao|   |--ISchoolDao.java|   |--IUserDao.java|--mediator|   |--Configuration.java|   |--DefaultSqlSession.java|   |--Resources.java|   |--SqlSession.java|   |--SqlSessionFactory.java|   |--SqlSessionFactoryBuilder.java|   |--XNode.java|--po|   |--School.java|   |--User.java|--resources|--mapper|   |--School_Mapper.xml|   |--User_Mapper.xml|--mybatis-config-datasource.xml|--test|--java|--com.lino.design.test|--ApiTest.java

4.2 中介者工程结构图

请添加图片描述

  • ORM 框架实现的核心类,包括:加载配置文件、解析 XML、获取数据库 Session、操作数据库及返回结果。
  • 左上方是对数据库的定义和处理,基本包括常用的方法:<T> T selectOne<T> List<T> selectList
  • 右侧是对数据库配置开启 Session 的工厂处理类,这里的工厂会操作 DefaultSqlSession
  • 之后是工厂建造者类 SqlSessionFactoryBuilder,是对数据库操作的核心类:处理工厂、解析文件和获取 Session 等。

4.3 资源和配置类

4.3.1 XML配置对象

XNode.java

package com.lino.design.mediator;import java.util.Map;/*** @description: xml配置对象*/
public class XNode {private String namespace;private String id;private String parameterType;private String resultType;private String sql;private Map<Integer, String> parameter;public String getNamespace() {return namespace;}public void setNamespace(String namespace) {this.namespace = namespace;}public String getId() {return id;}public void setId(String id) {this.id = id;}public String getParameterType() {return parameterType;}public void setParameterType(String parameterType) {this.parameterType = parameterType;}public String getResultType() {return resultType;}public void setResultType(String resultType) {this.resultType = resultType;}public String getSql() {return sql;}public void setSql(String sql) {this.sql = sql;}public Map<Integer, String> getParameter() {return parameter;}public void setParameter(Map<Integer, String> parameter) {this.parameter = parameter;}
}

4.3.2 资源工具类

Resources.java

package com.lino.design.mediator;import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;/*** @description: 资源类*/
public class Resources {public static Reader getResourceAsReader(String resource) throws IOException {return new InputStreamReader(getResourceAsStream(resource));}private static InputStream getResourceAsStream(String resource) throws IOException {ClassLoader[] classLoaders = getClassLoaders();for (ClassLoader classLoader : classLoaders) {InputStream inputStream = classLoader.getResourceAsStream(resource);if (null != inputStream) {return inputStream;}}throw new IOException("Could not find resource " + resource);}private static ClassLoader[] getClassLoaders() {return new ClassLoader[]{ClassLoader.getSystemClassLoader(),Thread.currentThread().getContextClassLoader()};}
}

4.3.3 配置类

Configuration.java

package com.lino.design.mediator;import java.sql.Connection;
import java.util.Map;/*** @description: 配置类*/
public class Configuration {protected Connection connection;protected Map<String, String> dataSource;protected Map<String, XNode> mapperElement;public void setConnection(Connection connection) {this.connection = connection;}public void setDataSource(Map<String, String> dataSource) {this.dataSource = dataSource;}public void setMapperElement(Map<String, XNode> mapperElement) {this.mapperElement = mapperElement;}
}

4.4 SqlSession实现

4.4.1 定义SqlSession接口

SqlSession.java

package com.lino.design.mediator;import java.util.List;/*** @description: 定义sqlsession接口*/
public interface SqlSession {/*** 查询单条记录** @param statement SQL语句* @param <T>       泛型* @return 泛型结果对象*/<T> T selectOne(String statement);/*** 查询单条记录** @param statement SQL语句* @param parameter 查询参数* @param <T>       泛型* @return 泛型结果对象*/<T> T selectOne(String statement, Object parameter);/*** 查询多条记录** @param statement SQL语句* @param <T>       泛型* @return 泛型集合对象*/<T> List<T> selectList(String statement);/*** 查询多条记录** @param statement SQL语句* @param parameter 查询参数* @param <T>       泛型* @return 泛型集合对象*/<T> List<T> selectList(String statement, Object parameter);/*** 关闭连接*/void close();
}
  • 定义了操作数据库的查询接口,分为查询一个结果和多个结果,同时包括有参数方法和无参数方法。

4.4.2 SqlSession具体实现类

DefaultSqlSession.java

package com.lino.design.mediator;import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.sql.*;
import java.util.*;
import java.util.Date;/*** @description: 默认SqlSession实现类*/
public class DefaultSqlSession implements SqlSession {private Connection connection;private Map<String, XNode> mappperElement;public DefaultSqlSession(Connection connection, Map<String, XNode> mappperElement) {this.connection = connection;this.mappperElement = mappperElement;}@Overridepublic <T> T selectOne(String statement) {try {XNode xNode = mappperElement.get(statement);PreparedStatement preparedStatement = connection.prepareStatement(xNode.getSql());ResultSet resultSet = preparedStatement.executeQuery();List<T> objects = resultSet2Obj(resultSet, Class.forName(xNode.getResultType()));return objects.get(0);} catch (Exception e) {e.printStackTrace();}return null;}@Overridepublic <T> T selectOne(String statement, Object parameter) {XNode xNode = mappperElement.get(statement);Map<Integer, String> parameterMap = xNode.getParameter();try {PreparedStatement preparedStatement = connection.prepareStatement(xNode.getSql());buildParameter(preparedStatement, parameter, parameterMap);ResultSet resultSet = preparedStatement.executeQuery();List<T> objects = resultSet2Obj(resultSet, Class.forName(xNode.getResultType()));return objects.get(0);} catch (Exception e) {e.printStackTrace();}return null;}@Overridepublic <T> List<T> selectList(String statement) {XNode xNode = mappperElement.get(statement);try {PreparedStatement preparedStatement = connection.prepareStatement(xNode.getSql());ResultSet resultSet = preparedStatement.executeQuery();return resultSet2Obj(resultSet, Class.forName(xNode.getResultType()));} catch (Exception e) {e.printStackTrace();}return null;}@Overridepublic <T> List<T> selectList(String statement, Object parameter) {XNode xNode = mappperElement.get(statement);Map<Integer, String> parameterMap = xNode.getParameter();try {PreparedStatement preparedStatement = connection.prepareStatement(xNode.getSql());buildParameter(preparedStatement, parameter, parameterMap);ResultSet resultSet = preparedStatement.executeQuery();return resultSet2Obj(resultSet, Class.forName(xNode.getResultType()));} catch (Exception e) {e.printStackTrace();}return null;}@Overridepublic void close() {}private void buildParameter(PreparedStatement preparedStatement, Object parameter, Map<Integer, String> parameterMap) throws SQLException, IllegalAccessException {int size = parameterMap.size();// 单个参数if (parameter instanceof Long) {for (int i = 1; i <= size; i++) {preparedStatement.setLong(i, Long.parseLong(parameter.toString()));}return;}if (parameter instanceof Integer) {for (int i = 1; i <= size; i++) {preparedStatement.setInt(i, Integer.parseInt(parameter.toString()));}return;}if (parameter instanceof String) {for (int i = 1; i <= size; i++) {preparedStatement.setString(i, parameter.toString());}return;}Map<String, Object> fieldMap = new HashMap<>();// 对象参数Field[] declaredFields = parameter.getClass().getDeclaredFields();for (Field field : declaredFields) {String name = field.getName();field.setAccessible(true);Object obj = field.get(parameter);field.setAccessible(false);fieldMap.put(name, obj);}for (int i = 1; i <= size; i++) {String parameterDefine = parameterMap.get(i);Object obj = fieldMap.get(parameterDefine);if (obj instanceof Short) {preparedStatement.setShort(i, Short.parseShort(obj.toString()));continue;}if (obj instanceof Integer) {preparedStatement.setInt(i, Integer.parseInt(obj.toString()));continue;}if (obj instanceof Long) {preparedStatement.setLong(i, Long.parseLong(obj.toString()));continue;}if (obj instanceof String) {preparedStatement.setString(i, obj.toString());continue;}if (obj instanceof Date) {preparedStatement.setDate(i, (java.sql.Date) obj);}}}private <T> List<T> resultSet2Obj(ResultSet resultSet, Class<?> clazz) {List<T> list = new ArrayList<>();try {ResultSetMetaData metaData = resultSet.getMetaData();int columnCount = metaData.getColumnCount();// 每次遍历行值while (resultSet.next()) {T obj = (T) clazz.newInstance();for (int i = 1; i <= columnCount; i++) {Object value = resultSet.getObject(i);String columnName = metaData.getColumnName(i);String setMethod = "set" + columnName.substring(0, 1).toUpperCase() + columnName.substring(1);Method method;if (value instanceof Timestamp) {method = clazz.getMethod(setMethod, Date.class);} else {method = clazz.getMethod(setMethod, value.getClass());}method.invoke(obj, value);}list.add(obj);}} catch (Exception e) {e.printStackTrace();}return list;}
}
  • 这里包括了接口定义的方法实现,即包装了 JDBC 层的使用。
    • 通过这种包装,可以隐藏数据库的 JDBC 操作,当外部调用时,对入参、出参都由内部处理。

4.4.3 定义 SqlSession 工厂接口

SqlSessionFactory.java

package com.lino.design.mediator;/*** @description: SqlSession工厂*/
public interface SqlSessionFactory {/*** 开启会话** @return 会话*/SqlSession openSession();
}

4.4.4 SqlSessionFactory工厂接口具体实现类

DefaultSqlSessionFactory.java

package com.lino.design.mediator;/*** @description: 默认SqlSession工厂实现类*/
public class DefaultSqlSessionFactory implements SqlSessionFactory {private final Configuration configuration;public DefaultSqlSessionFactory(Configuration configuration) {this.configuration = configuration;}@Overridepublic SqlSession openSession() {return new DefaultSqlSession(configuration.connection, configuration.mapperElement);}
}
  • DefaultSqlSessionFactoryMyBatis 最常用的类,这里简单地实现了一个版本。
  • 虽然是简单版本,但包括了最基本的核心思路。
    • 当开启 SqlSession 时,会返回一个 DefaultSqlSession
    • 这个构造函数向下传递了 Configuration 配置文件,包括:
      • Connection connection
      • Map<String, String> dataSource
      • Map<String, XNode> mapperElement

4.4.5 SqlSessionFactoryBuilder 建造者工厂实现

SqlSessionFactoryBuilder.java

package com.lino.design.mediator;import org.apache.ibatis.builder.xml.XMLMapperEntityResolver;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.xml.sax.InputSource;
import java.io.Reader;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;import static java.util.regex.Pattern.*;/*** @description: SqlSession工厂建造者类*/
public class SqlSessionFactoryBuilder {public DefaultSqlSessionFactory build(Reader reader) {SAXReader saxReader = new SAXReader();try {saxReader.setEntityResolver(new XMLMapperEntityResolver());Document document = saxReader.read(new InputSource(reader));Configuration configuration = parseConfiguration(document.getRootElement());return new DefaultSqlSessionFactory(configuration);} catch (DocumentException e) {e.printStackTrace();}return null;}private Configuration parseConfiguration(Element root) {Configuration configuration = new Configuration();configuration.setDataSource(dataSource(root.selectNodes("//dataSource")));configuration.setConnection(connection(configuration.dataSource));configuration.setMapperElement(mapperElement(root.selectNodes("mappers")));return configuration;}private Map<String, String> dataSource(List<Element> list) {Map<String, String> dataSource = new HashMap<>(4);Element element = list.get(0);List content = element.content();for (Object o : content) {Element e = (Element) o;String name = e.attributeValue("name");String value = e.attributeValue("value");dataSource.put(name, value);}return dataSource;}private Connection connection(Map<String, String> dataSource) {try {Class.forName(dataSource.get("driver"));return DriverManager.getConnection(dataSource.get("url"), dataSource.get("username"), dataSource.get("password"));} catch (ClassNotFoundException | SQLException e) {e.printStackTrace();}return null;}private Map<String, XNode> mapperElement(List<Element> list) {Map<String, XNode> map = new HashMap<>(16);Element element = list.get(0);List content = element.content();for (Object o : content) {Element e = (Element) o;String resource = e.attributeValue("resource");try {Reader reader = Resources.getResourceAsReader(resource);SAXReader saxReader = new SAXReader();Document document = saxReader.read(new InputSource(reader));Element root = document.getRootElement();// 命名空间String namespace = root.attributeValue("namespace");// SELECTList<Element> selectNodes = root.selectNodes("select");for (Element node : selectNodes) {String id = node.attributeValue("id");String parameterType = node.attributeValue("parameterType");String resultType = node.attributeValue("resultType");String sql = node.getText();// ? 匹配Map<Integer, String> parameter = new HashMap<>(16);Pattern pattern = compile("(#\\{(.*?)})");Matcher matcher = pattern.matcher(sql);for (int i = 1; matcher.find(); i++) {String g1 = matcher.group(1);String g2 = matcher.group(2);parameter.put(i, g2);sql = sql.replace(g1, "?");}XNode xNode = new XNode();xNode.setNamespace(namespace);xNode.setId(id);xNode.setParameterType(parameterType);xNode.setResultType(resultType);xNode.setSql(sql);xNode.setParameter(parameter);map.put(namespace + "." + id, xNode);}} catch (Exception ex) {ex.printStackTrace();}}return map;}
}
  • 这个类包括的核心方法有:
    • build:构建实例化元素。
      • 这个类主要用于创建解析 XML 文件的类,以及初始化 SqlSession 工厂类 DefaultSqlSessionFactory
      • 另外,需要注意代码 saxReader.setEntityResolver(new XMLMapperEntityResolver()),是为了保证在不联网时同样可以解析 XML,否则会需要从互联网获取 dtd 文件。
    • parseConfiguration:解析配置。
      • 这个类时对 XML 中的元素进行获取,这里主要获取了 dataSourcemappers 两个配置。
        • dataSource:数据库的链接信息。
        • mappers:对数据库操作语句的解析。
    • dataSource:获取数据库配置。
    • connection(Map<String, String> dataSource):连接数据库。
      • 数据库连接开启操作的地方和常见的方式是一样的:Class.forName(dataSource.get("driver"))
      • 但是这样包装以后,外部不需要知道具体是如何操作的。
      • 同时,当需要连接多套数据库时,也可以在这里扩展。
    • mapperElement:解析 SQL 语句。
      • 核心是为了解析 XML 中的 SQL 语句配置。
      • 在平常的使用中,基本都会配置一些 SQL 语句,也有一些入参的占位符。这里使用正则表达式的方式解析操作。
      • 解析完成的 SQL 语句就有了一个名称和 SQL 的映射关系,当操作数据库时,这个组件就可以通过映射关系获取对应的 SQL 语句。

4.5 数据库对象类和持久层

4.5.1 用户类

User.java

package com.lino.design.po;import java.util.Date;/*** @description: 用户类*/
public class User {private Long id;private String name;private Integer age;private Date createTime;private Date updateTime;public Long getId() {return id;}public void setId(Long id) {this.id = id;}public String getName() {return name;}public void setName(String name) {this.name = name;}public Integer getAge() {return age;}public void setAge(Integer age) {this.age = age;}public Date getCreateTime() {return createTime;}public void setCreateTime(Date createTime) {this.createTime = createTime;}public Date getUpdateTime() {return updateTime;}public void setUpdateTime(Date updateTime) {this.updateTime = updateTime;}
}

4.5.2 学校类

School.java

package com.lino.design.po;import java.util.Date;/*** @description: 学校类*/
public class School {private Long id;private String name;private String address;private Date createTime;private Date updateTime;public Long getId() {return id;}public void setId(Long id) {this.id = id;}public String getName() {return name;}public void setName(String name) {this.name = name;}public String getAddress() {return address;}public void setAddress(String address) {this.address = address;}public Date getCreateTime() {return createTime;}public void setCreateTime(Date createTime) {this.createTime = createTime;}public Date getUpdateTime() {return updateTime;}public void setUpdateTime(Date updateTime) {this.updateTime = updateTime;}
}

4.5.3 用户持久层接口

IUserDao.java

package com.lino.design.dao;import com.lino.design.po.User;
import java.util.List;/*** @description: 用户持久层接口*/
public interface IUserDao {/*** 根据用户ID查询用户信息** @param id 用户ID* @return 用户信息对象*/User queryUserInfoById(Long id);/*** 查询用户列表** @param user 用户对象* @return 用户列表*/List<User> queryUserList(User user);
}

4.5.4 学校持久层接口

ISchoolDao.java

package com.lino.design.dao;import com.lino.design.po.School;/*** @description: 学校持久层接口*/
public interface ISchoolDao {/*** 根据学校ID查询学校信息** @param treeId 学校ID* @return 学校信息对象*/School querySchoolInfoById(Long treeId);
}

4.5.5 用户持久层配置文件

User_Mapper.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.lino.design.dao.IUserDao"><select id="queryUserInfoById" parameterType="java.lang.Long" resultType="com.lino.design.po.User">SELECT id, name, age, createTime, updateTimeFROM userwhere id = #{id}</select><select id="queryUserList" parameterType="com.lino.design.po.User" resultType="com.lino.design.po.User">SELECT id, name, age, createTime, updateTimeFROM userwhere age = #{age}</select></mapper>

4.5.6 学校持久层配置文件

School_Mapper.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.lino.design.dao.ISchoolDao"><select id="querySchoolInfoById" resultType="com.lino.design.po.School">SELECT id, name, address, createTime, updateTimeFROM schoolwhere id = #{id}</select></mapper>

4.6 单元测试

4.6.1 配置文件

mybatis-config-datasource.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN""http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration><environments default="development"><environment id="development"><transactionManager type="JDBC"></transactionManager><dataSource type="POOLED"><property name="driver" value="com.mysql.jdbc.Driver"/><property name="url" value="jdbc:mysql://127.0.0.1:3306/design?useUnicode=true"/><property name="username" value="root"/><property name="password" value="123456"/></dataSource></environment></environments><mappers><mapper resource="mapper/User_Mapper.xml" /><mapper resource="mapper/School_Mapper.xml" /></mappers>
</configuration>
  • 配置文件包括了数据库的连接池信息及需要引入的 mapper 映射文件。

4.6.2 单条数据查询单元测试

ApiTest.java

@Test
public void test_queryUserInfoById() {String resource = "mybatis-config-datasource.xml";Reader reader;try {reader = Resources.getResourceAsReader(resource);DefaultSqlSessionFactory sqlMapper = new SqlSessionFactoryBuilder().build(reader);SqlSession session = sqlMapper.openSession();try {User user = session.selectOne("com.lino.design.dao.IUserDao.queryUserInfoById", 1L);logger.info("测试结果:{}", JSON.toJSONString(user));} finally {session.close();reader.close();}} catch (IOException e) {e.printStackTrace();}
}
  • 单元测试包括:资源加载和解析、SqlSession 工厂构建、开启 SqlSession 以及最后执行查询操作 selectOne

测试结果

08:37:28.528 [main] INFO  com.lino.design.test.ApiTest - 测试结果:{"age":18,"createTime":1675155123000,"id":1,"name":"张三","updateTime":1675155126000}

4.6.3 集合数据查询单元测试

ApiTest.java

@Test
public void test_queryUserList() {String resource = "mybatis-config-datasource.xml";Reader reader;try {reader = Resources.getResourceAsReader(resource);DefaultSqlSessionFactory sqlMapper = new SqlSessionFactoryBuilder().build(reader);SqlSession session = sqlMapper.openSession();try {User req = new User();req.setAge(18);List<User> userList = session.selectList("com.lino.design.dao.IUserDao.queryUserList", req);logger.info("测试结果:{}", JSON.toJSONString(userList));} finally {session.close();reader.close();}} catch (IOException e) {e.printStackTrace();}
}
  • 集合测试内容与以上的查询方法有所不同。session.selectList 是查询一个集合结果。

测试结果

08:37:41.751 [main] INFO  com.lino.design.test.ApiTest - 测试结果:[{"age":18,"createTime":1675155123000,"id":1,"name":"张三","updateTime":1675155126000},{"age":18,"createTime":1675155136000,"id":2,"name":"李四","updateTime":1675155138000}]

五、总结:中介者模式

  • 运用中介者模式的设计思想手写了一个 ORM 框架,隐去了对数据库的复杂操作,让外部的调用方能够非常简单地操作数据库,这也是平常使用 Mybatis 的效果。
  • 中介者模式还可以实现服务接口的包装。
    • 比如:公司有很多的奖品接口需要在营销活动中对接,可以把这些奖品接口统一汇总到中台再开发一个奖品中心,对外提供服务。
    • 这样就不需要每一位研发人员都去找奖品接口提供方,而是找中台服务即可。
  • 中介者模式:满足了单一职责和开闭,也就符合了迪米特法则,即越少人知道越好。
    • 外部的人只需要按照需求调用,不需要知道具体是如何实现的,复杂的内容由组件合作服务平台处理即可。

相关文章:

Java设计模式:四、行为型模式-04:中介者模式

文章目录 一、定义&#xff1a;中介者模式二、模拟场景&#xff1a;中介者模式三、违背方案&#xff1a;中介者模式3.1 工程结构3.2 创建数据库3.3 JDBC工具类3.4 单元测试 四、改善代码&#xff1a;中介者模式4.1 工程结构4.2 中介者工程结构图4.3 资源和配置类4.3.1 XML配置对…...

【GO】LGTM_Grafana_Tempo(1)_架构

最近在尝试用 LGTM 来实现 Go 微服务的可观测性&#xff0c;就顺便整理一下文档。 Tempo 会分为 4 篇文章&#xff1a; Tempo 的架构官网测试实操跑通gin 框架发送 trace 数据到 tempogo-zero 微服务框架使用发送数据到 tempo 第一篇是关于&#xff0c;tempo 的架构&#xff…...

MFC 与 QT“常用控件”对比

1、 常用控件 MFC QT 1.静态文本框/标签 CStatic QLabel 按钮 CButton包含了3种样式的按钮&#xff0c;Push Button&#xff0c;Check Box&#xff0c;Radio Box 4种不同的类 2.按钮&#xff1a;推动按钮 Push Button(同一个类CButton) QPushButton 3.按钮&#xf…...

linux 下安装chrome 和 go

1. 安装google-chrome 1.1 首先下载google-chrome.deb安装包 之后 安装 gdebi包 sudo apt install gdebi 1.2 安装所要安装的软件 sudo gdebi code_1.81.1-1691620686_amd64.deb 1.3 解决Chrome无法启动问题 rootubuntu:~/Downloads# whereis google-chrome google-chrome…...

OpenCV: cv2.findContours - ValueError: too many values to unpack

OpenCV找轮廓findContours报错 ValueError: not enough values to unpack (expected 3,got 2) 问题指向这行代码&#x1f447; binary, cnts, hierarchy cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE ) 报错的意思是需要3个返回值但只给了两…...

Vue框架--Vue概述

1.vue概述 Vue是一个渐进式JavaScript 框架,用于动态构建用户界面。 2.vue的特点 (1).遵循MVVM模式 MVVM是Model-View-ViewModel的简写。它本质上就是MVC的改进版 (2).采用组件化模式,提高代码的复用率,且让代码更好的维护。 组件化:简单的说就是使用xxx.vue模式包含一个页面…...

Fiddler安装与使用教程(1) —— 软测大玩家

&#x1f60f;作者简介&#xff1a;博主是一位测试管理者&#xff0c;同时也是一名对外企业兼职讲师。 &#x1f4e1;主页地址&#xff1a;【Austin_zhai】 &#x1f646;目的与景愿&#xff1a;旨在于能帮助更多的测试行业人员提升软硬技能&#xff0c;分享行业相关最新信息。…...

Ubuntu 22.04安装 —— Win11 22H2

目录 Ubuntu使用下载UbuntuVmware 安装图示安装步骤图示 Ubuntu使用 系统环境&#xff1a; Windows 11 22H2Vmware 17 ProUbutun 22.04.3 Server Ubuntu Server documentation | Ubuntu 下载 Ubuntu 官网下载 建议安装长期支持版本 ——> 可以选择桌面版或服务器版(仅包…...

【STM32】IIC的初步使用

IIC简介 物理层 连接多个devices 它是一个支持设备的总线。“总线”指多个设备共用的信号线。在一个 I2C 通讯总线中&#xff0c;可连接多个 I2C 通讯设备&#xff0c;支持多个通讯主机及多个通讯从机。 两根线 一个 I2C 总线只使用两条总线线路&#xff0c;一条双向串行数…...

音视频 ffmpeg命令参数说明

主要参数&#xff1a; -i 设定输入流 -f 设定输出格式(format) -ss 开始时间 -t 时间长度 音频参数&#xff1a; -aframes 设置要输出的音频帧数 -b:a 音频码率 -ar 设定采样率 -ac 设定声音的Channel数 -acodec 设定声音编解码器&#xff0c;如果用copy表示原始编解码数据必须…...

Go学习第十天

打印报错堆栈信息 安装errors包 go get github.com/pkg/errors 具体使用 // 新生成一个错误, 带堆栈信息 func New(message string) error//只附加新的信息 func WithMessage(err error, message string) error//只附加调用堆栈信息 func WithStack(err error) error//同时附…...

pytorch中 nn.Conv2d的简单用法

torch.nn.Conv2d(in_channels, out_channels, kernel_size, stride1, padding0, dilation1, groups1, biasTrue,padding_modezeros)参数介绍&#xff1a; in_channels&#xff1a;卷积层输入通道数 out_channels&#xff1a;卷积层输出通道数 kernel_size&#xff1a;卷积层的…...

前端项目工程化之代码规范

目录 一、前言二、ESLint三、Prettier四、项目实战4.1 环境依赖版本4.2 使用pnpm4.3 git提交规范 五、资源 收集六、源码地址 一、前言 前端项目工程化之代码规范是指在前端项目中定义一套代码规范&#xff0c;以确保项目中的代码风格和格式一致&#xff0c;提高代码的可读性和…...

MyBaits Generator

参考文档 MyBatis Generator Core – Introduction to MyBatis Generator MyBatis Generator 详解_enablesubpackages_isea533的博客-CSDN博客 一文解析 MyBatis Generator 的使用及配置 - 掘金 1. Introduction MyBatis Generator (MBG) 是 MyBatis MyBatis的代码生成器。…...

JavaWeb 速通Ajax

目录 一、Ajax快速入门 1.基本介绍 : 2.使用原理 : 二、Ajax经典入门案例 1.需求 : 2.前端页面实现 : 3. 处理HTTP请求的servlet实现 4.引入jar包及druid配置文件、工具类 : 5.Domain层实现 : 6.DAO层实现 : 7.Service层实现 : 8.运行测试 : 三、JQuery操作Ajax 1 …...

vscode c++编译时报错

文章目录 1. 报错内容&#xff1a;GDB Failed with message;2. 报错内容&#xff1a;Unable to start debugging. 1. 报错内容&#xff1a;GDB Failed with message; 例如上图报错&#xff0c;一般就是编译器选择错误&#xff0c;有两种方法解决&#xff1a; 打开 tasks.json …...

基于体系结构架构设计-架构真题(十五)

基于体系结构开发设计&#xff08;Architecture-Base Software Design&#xff09;ABSD&#xff0c;是指构成体系结构的&#xff08;&#xff09;组合驱动&#xff0c;ABSC方法是一个自项向下、递归细化的方法&#xff0c;软件系统的体系结构通过该方法细化&#xff0c;直到能产…...

IPv6网络实验:地址自动生成与全球单播通信探索

文章目录 一、实验背景与目的二、实验拓扑三、实验需求四、实验解法1. 在R1和PC3上开启IPv6链路本地地址自动生成&#xff0c;测试是否能够使用链路本地地址互通2. 为R1配置全球单播地址2001::1/64&#xff0c;使PC3能够自动生成与R1同一网段的IPv6地址3. 测试R1和PC3是否能够使…...

深入探索前端之道:JavaScript深拷贝与浅拷贝的解析与实现

引言 前端开发中&#xff0c;数据的复制是一个常见的操作。尤其是在处理对象和数组时&#xff0c;我们需要考虑的是一个浅拷贝还是深拷贝。那么&#xff0c;什么是深拷贝和浅拷贝&#xff1f;它们在前端开发中有什么作用&#xff1f;如何实现这两种拷贝&#xff1f;这是我们在…...

关于两个不同数据库的两张表建立数据库链接,关联查询数据

一、数据库链接 数据库链接&#xff08;database link&#xff09;是用于跨不同数据库之间进行连接和数据传输的工具或方法。它允许在一个数据库中访问另一个数据库中的对象和数据。 二、具体操作 以Oracle数据库为例 --1.建立链接tjpt CREATE DATABASE LINK tjpt CONNECT…...

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器的上位机配置操作说明

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器专为工业环境精心打造&#xff0c;完美适配AGV和无人叉车。同时&#xff0c;集成以太网与语音合成技术&#xff0c;为各类高级系统&#xff08;如MES、调度系统、库位管理、立库等&#xff09;提供高效便捷的语音交互体验。 L…...

CTF show Web 红包题第六弹

提示 1.不是SQL注入 2.需要找关键源码 思路 进入页面发现是一个登录框&#xff0c;很难让人不联想到SQL注入&#xff0c;但提示都说了不是SQL注入&#xff0c;所以就不往这方面想了 ​ 先查看一下网页源码&#xff0c;发现一段JavaScript代码&#xff0c;有一个关键类ctfs…...

【第二十一章 SDIO接口(SDIO)】

第二十一章 SDIO接口 目录 第二十一章 SDIO接口(SDIO) 1 SDIO 主要功能 2 SDIO 总线拓扑 3 SDIO 功能描述 3.1 SDIO 适配器 3.2 SDIOAHB 接口 4 卡功能描述 4.1 卡识别模式 4.2 卡复位 4.3 操作电压范围确认 4.4 卡识别过程 4.5 写数据块 4.6 读数据块 4.7 数据流…...

条件运算符

C中的三目运算符&#xff08;也称条件运算符&#xff0c;英文&#xff1a;ternary operator&#xff09;是一种简洁的条件选择语句&#xff0c;语法如下&#xff1a; 条件表达式 ? 表达式1 : 表达式2• 如果“条件表达式”为true&#xff0c;则整个表达式的结果为“表达式1”…...

Springcloud:Eureka 高可用集群搭建实战(服务注册与发现的底层原理与避坑指南)

引言&#xff1a;为什么 Eureka 依然是存量系统的核心&#xff1f; 尽管 Nacos 等新注册中心崛起&#xff0c;但金融、电力等保守行业仍有大量系统运行在 Eureka 上。理解其高可用设计与自我保护机制&#xff0c;是保障分布式系统稳定的必修课。本文将手把手带你搭建生产级 Eur…...

【Zephyr 系列 10】实战项目:打造一个蓝牙传感器终端 + 网关系统(完整架构与全栈实现)

🧠关键词:Zephyr、BLE、终端、网关、广播、连接、传感器、数据采集、低功耗、系统集成 📌目标读者:希望基于 Zephyr 构建 BLE 系统架构、实现终端与网关协作、具备产品交付能力的开发者 📊篇幅字数:约 5200 字 ✨ 项目总览 在物联网实际项目中,**“终端 + 网关”**是…...

什么是Ansible Jinja2

理解 Ansible Jinja2 模板 Ansible 是一款功能强大的开源自动化工具&#xff0c;可让您无缝地管理和配置系统。Ansible 的一大亮点是它使用 Jinja2 模板&#xff0c;允许您根据变量数据动态生成文件、配置设置和脚本。本文将向您介绍 Ansible 中的 Jinja2 模板&#xff0c;并通…...

Maven 概述、安装、配置、仓库、私服详解

目录 1、Maven 概述 1.1 Maven 的定义 1.2 Maven 解决的问题 1.3 Maven 的核心特性与优势 2、Maven 安装 2.1 下载 Maven 2.2 安装配置 Maven 2.3 测试安装 2.4 修改 Maven 本地仓库的默认路径 3、Maven 配置 3.1 配置本地仓库 3.2 配置 JDK 3.3 IDEA 配置本地 Ma…...

基于 TAPD 进行项目管理

起因 自己写了个小工具&#xff0c;仓库用的Github。之前在用markdown进行需求管理&#xff0c;现在随着功能的增加&#xff0c;感觉有点难以管理了&#xff0c;所以用TAPD这个工具进行需求、Bug管理。 操作流程 注册 TAPD&#xff0c;需要提供一个企业名新建一个项目&#…...

ZYNQ学习记录FPGA(一)ZYNQ简介

一、知识准备 1.一些术语,缩写和概念&#xff1a; 1&#xff09;ZYNQ全称&#xff1a;ZYNQ7000 All Pgrammable SoC 2&#xff09;SoC:system on chips(片上系统)&#xff0c;对比集成电路的SoB&#xff08;system on board&#xff09; 3&#xff09;ARM&#xff1a;处理器…...