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
- 从测试结果可以看到,已经查询到了数据库中的数据。
- 但如果全部的业务开发都这样实现,会非常麻烦。实际的开发中,会使用相应的框架,比如 MyBatis、IBatis 和 Hibernate 等。
四、改善代码:中介者模式
- 使用中介者模式模仿 MyBatis 的 ORM 框架的开发。
- 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);}
}
- DefaultSqlSessionFactory 事 MyBatis 最常用的类,这里简单地实现了一个版本。
- 虽然是简单版本,但包括了最基本的核心思路。
- 当开启 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 中的元素进行获取,这里主要获取了
dataSource
、mappers
两个配置。dataSource
:数据库的链接信息。mappers
:对数据库操作语句的解析。
- 这个类时对 XML 中的元素进行获取,这里主要获取了
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:中介者模式
文章目录 一、定义:中介者模式二、模拟场景:中介者模式三、违背方案:中介者模式3.1 工程结构3.2 创建数据库3.3 JDBC工具类3.4 单元测试 四、改善代码:中介者模式4.1 工程结构4.2 中介者工程结构图4.3 资源和配置类4.3.1 XML配置对…...

【GO】LGTM_Grafana_Tempo(1)_架构
最近在尝试用 LGTM 来实现 Go 微服务的可观测性,就顺便整理一下文档。 Tempo 会分为 4 篇文章: Tempo 的架构官网测试实操跑通gin 框架发送 trace 数据到 tempogo-zero 微服务框架使用发送数据到 tempo 第一篇是关于,tempo 的架构ÿ…...

MFC 与 QT“常用控件”对比
1、 常用控件 MFC QT 1.静态文本框/标签 CStatic QLabel 按钮 CButton包含了3种样式的按钮,Push Button,Check Box,Radio Box 4种不同的类 2.按钮:推动按钮 Push Button(同一个类CButton) QPushButton 3.按钮…...

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) 问题指向这行代码👇 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) —— 软测大玩家
😏作者简介:博主是一位测试管理者,同时也是一名对外企业兼职讲师。 📡主页地址:【Austin_zhai】 🙆目的与景愿:旨在于能帮助更多的测试行业人员提升软硬技能,分享行业相关最新信息。…...

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

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

音视频 ffmpeg命令参数说明
主要参数: -i 设定输入流 -f 设定输出格式(format) -ss 开始时间 -t 时间长度 音频参数: -aframes 设置要输出的音频帧数 -b:a 音频码率 -ar 设定采样率 -ac 设定声音的Channel数 -acodec 设定声音编解码器,如果用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)参数介绍: in_channels:卷积层输入通道数 out_channels:卷积层输出通道数 kernel_size:卷积层的…...

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

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. 报错内容:GDB Failed with message;2. 报错内容:Unable to start debugging. 1. 报错内容:GDB Failed with message; 例如上图报错,一般就是编译器选择错误,有两种方法解决: 打开 tasks.json …...

基于体系结构架构设计-架构真题(十五)
基于体系结构开发设计(Architecture-Base Software Design)ABSD,是指构成体系结构的()组合驱动,ABSC方法是一个自项向下、递归细化的方法,软件系统的体系结构通过该方法细化,直到能产…...

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

深入探索前端之道:JavaScript深拷贝与浅拷贝的解析与实现
引言 前端开发中,数据的复制是一个常见的操作。尤其是在处理对象和数组时,我们需要考虑的是一个浅拷贝还是深拷贝。那么,什么是深拷贝和浅拷贝?它们在前端开发中有什么作用?如何实现这两种拷贝?这是我们在…...

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

Google登录SDK
一、接入的准备工作 官方文档链接地址:开始使用一键登录和注册 按照步骤进行接入即可 二、项目参考(Unity项目) 注意:代码版本如果不适用新的Google API 请自行参考最新版本接口 SDKGoogleSignInActivity 主要用于登录的代码。Un…...

ASP.NET Core 8 的运行环境 Environment
开发流程一般有3个阶段: 开发 Development测试 Stage正式 Production 运行时环境变量可以用于根据不同的开发阶段运行不同的逻辑,比如在开发阶段的某些功能或保密信息不暴露在正式上线的代码中。 在Visual Stduio创建的模板代码中是否为开发环境Envir…...

机械臂手眼标定ZED相机——眼在手外python、matlab
目录 1.眼在手外原理 2.附上眼在手外求得手眼矩阵的python代码 3.眼在手外标定步骤 1)打印棋盘格 2)得到hand数据 3)得到camera数据 4.运行python得到手眼矩阵 1.眼在手外原理 眼在手外所求的手眼矩阵是基坐标到相机的转换矩阵 2.附上…...

前端实现动态路由(前端控制全部路由,后端返回用户角色)
优缺点 优点: 不用后端帮助,路由表维护在前端逻辑相对比较简单,比较容易上手权限少的系统用前端鉴权更加方便 缺点: 线上版本每次修改权限页面,都需要重新打包项目大型项目不适用如果需要在页面中增加角色并且控制可以访问的页…...

Spring5学习笔记—Spring事务处理
✅作者简介:大家好,我是Leo,热爱Java后端开发者,一个想要与大家共同进步的男人😉😉 🍎个人主页:Leo的博客 💞当前专栏: Spring专栏 ✨特色专栏: M…...

如何增长LLM推理token,从直觉到数学
背景: 最近大模型输入上文长度增长技术点的研究很火。为何要增长token长度,为何大家如此热衷于增长输入token的长度呢?其实你如果是大模型比价频繁的使用者,这个问题应该不难回答。增长了输入token的长度,那需要多次出入才能得到…...

《穷爸爸与富爸爸》时间是最宝贵的资产,只有它对所有人都是公平的
《穷爸爸与富爸爸》时间是最宝贵的资产,只有它对所有人都是公平的 罗伯特清崎,日裔美国人,投资家、教育家、企业家。 萧明 译 文章目录 《穷爸爸与富爸爸》时间是最宝贵的资产,只有它对所有人都是公平的[toc]摘录各阶层现金流图支…...

Git结合Gitee的企业开发模拟
本系列有两篇文章: 一是另外一篇《快速使用Git完整开发》,主要说明了关于Git工具的基础使用,包含三板斧(git add、git commit、git push)、Git基本配置、版本回退、分支管理、公钥与私钥、远端仓库和远端分支、忽略文…...

WEBGL(2):绘制单个点
代码如下: <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content"IEedge"><meta name"viewport" content"widthdevi…...

C# task多线程创建,暂停,继续,结束使用
1、多线程任务创建 private void button1_Click(object sender, EventArgs e) //创建线程{CancellationToken cancellationToken tokensource.Token;Task.Run(() > //模拟耗时任务{for (int i 0; i < 100; i){if (cancellationToken.IsCancellationRequested){return;…...