理解MyBatis原理、思想

news2024/11/27 16:29:27

JDBC编码的缺点

  • 结果集解析复杂,列名硬编码,sql变化导致解析代码变化,系统不易维护。
  • Sql语句硬编码,难以维护。 数据库配置硬编码。
  • 频繁连接、释放数据库资源,降低系统性能。
  • preparedStatement向占位符号传参数存在硬编码,不易维护。

MyBatis的核心原理、思想

MyBatis的出现,使得我们不用在代码中进行SQL语句硬编码,代码更加容易维护,更好的管理数据库资源,提升性能。

手写一个简单的ORM框架,加深理解

用例分析

框架写出来是给开发人员用的,使用过程无非 导包、配置、按规则编写代码、运行。
image.png

功能分析

设计一个最小可用的仿MyBatis的ORM框架,那首先我们需要拿到数据源的配置信息、Mapper配置的信息,然后在调用Mapper方法的时候,解析方法参数,填充到Mapper方法对应的SQL中去,然后执行QL,拿到结果之后,按照配置解析并映射到对应的Java对象。

功能大概:
A_2023-12-04_17-25-08.png

代码分析、设计

1.格式约定

首先,我们要约定数据源、Mapper配置信息的格式。

  • 存储数据源信息、Mapper 配置信息的格式
<?xml version="1.0" encoding="UTF-8" ?>
<configuration>
  <!-- 数据源信息 -->
    <dataSource>
        <property name="driver" value="com.mysql.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://127.0.0.1:3306/ruoyi?characterEncoding=utf8"/>
        <property name="username" value="root"/>
        <property name="password" value="123456"/>
    </dataSource>

    <!-- Mapper XML文件配置信息 -->
    <mappers>
        <mapper resource="SysRoleMapper.xml"/>
    </mappers>
</configuration>

  • 具体的Mapper XML文件的格式
<?xml version="1.0" encoding="UTF-8" ?>
<mapper namespace="com.hero.dao.SysRoleMapper">
  <!-- id:方法名称; resultType: 查询结果返回值-->
    <select id="findAll" resultType="com.hero.dao.SysRole">
        select * from sys_role;
    </select>
</mapper>
2.数据对象

然后,我们需要一个存储数据源信息、Mapper信息、SQL信息的载体,简单来说 XML -> Java对象。

  • Configuration类存储了数据源信息、Mapper XML解析之后的具体方法信息。其中,sqlSourceMap对象的key存储Mapper XML中方法的全路径,比如:com.hero.dao.SysRoleMapper.findAll,而value存储SqlSource对象,此对象有 具体sql 以及 查询的结果类型。

image.png

  • SqlSource类存储了Mapper XML 中方法对应对应的sql,以及方法结果类型。

image.png

从json格式的角度来看Configuration对象的数据的格式,这样更好理解一些:

{
  "driver": "xxx",
  "url": "xxx",
  "sqlSourceMap": {
    "com.hero.dao.SysRoleMapper.findAll": {
        "sql": "select * from sys_role",
        "reultType": ""
    }
  },
  "password": "xxx",
  "username": "xxxx"
}
3.数据源加载、装配Mapper、解析SQL

有了载体之后,我们要实现 加载数据源配置 、 **装配Mapper XML功能、解析方法对应的SQL **,在这里,由SqlSessionFactoryBuilder类负载从xml文件中加载数据源信息 并 装配Mapper信息,统一写入Configuration对象。
image.png

4.通用SQL方法接口与执行器

有了上述信息,我们就可以创建执行SQL语句的规范接口SqlSession,此接口提供了通用的方法去处理业务的CRUD语句,默认实现类是DefaultSqlSessionImpl,接口方法传入 Mapper XML方法 全路径,实现类将全路径传递给Executor类,Executor会获取到具体的SQL,进行参数填充,并发送SQL到数据库,然后拿到执行结果,进行结果映射,最后返回给实现类。

在这里,我们不直接创建SqlSession实现类,而是通过SqlSessionFactory工厂类创建。

  • SqlSession接口。除了通用CURD方法(select\update\delete\insert),getMapper 也是一个重要的方法,可以获取Mapper接口的代理实例,通过Mapper接口即可执行SQL,不用直接使用SqlSession接口,例如 List<SysRole> list = sysRoleMapper.findAll()

image.png

  • Executor类负责发送SQL到数据库,处理SQL的参数填充(绑定)、结果集的映射。

image.png

  • SqlSessionFactory通过openeSession()方法创建SqlSession对象

image.png

小结

通过SqlSessionFactoryBuilder类,我们将XML信息写入到Configuration对象,并构建一个SqlSessionFactory会话工厂,通过工厂类可以获取SqlSession接口具体实现类实例DefaultSqlSessionImplSqlSession接口提供了通用的CURD方法,而实现类依赖Executor执行器,实现参数填充、SQL发送、结果映射功能。
image.png
同时,我们提到了SqlSession的getMapper方法,在后面代码中会看到。

代码实现

Configuration
public class Configuration {
    private String driver;
    private String url;
    private String username;
    private String password;
    private Map<String, SqlSource> sqlSourceMap = new HashMap<>();
	// getting/setting
}
SqlSource
public class SqlSource {
    //存储了Sql语句
    private String sql;
    //存储封装JavaBean对象类的全限定名称
    private String resultType;
    // getting/setting
}
SqlSessionFactoryBuilder
public class SqlSessionFactoryBuilder {
    /**
     * 构建工厂对象
     * 参数:SqlMapConfig.xml配置文件的输入流对象
     */
    public SqlSessionFactory build(InputStream inputStream) throws
            DocumentException {
        Configuration configuration = new Configuration();
        //解析配置文件
        loadXmlConfig(configuration, inputStream);
        return new SqlSessionFactory(configuration);
    }

    /**
     * 解析框架使用者传入的配置文件
     */
    private void loadXmlConfig(Configuration configuration, InputStream
            inputStream) throws DocumentException {
        //创建解析XML文件对象SAXReader
        SAXReader saxReader = new SAXReader();
        //读取SqlMapConfig.xml配置文件流资源,获取文档对象
        Document document = saxReader.read(inputStream);
        //获取SqlMapConfig.xml 配置文件内所有property标签元素
        List<Element> selectNodes = document.selectNodes("//property");
        //循环解析property标签内容,抽取配置信息
        for (Element element : selectNodes) {
            String name = element.attributeValue("name");
            if ("driver".equals(name)) {//数据库驱动
                configuration.setDriver(element.attributeValue("value"));
            } else if ("url".equals(name)) {//数据库地址
                configuration.setUrl(element.attributeValue("value"));
            } else if ("username".equals(name)) {//用户名
                configuration.setUsername(element.attributeValue("value"));
            } else if ("password".equals(name)) {//密码
                configuration.setPassword(element.attributeValue("value"));
            }
        }
        //解析SqlMapConfig.xml 映射器配置信息
        List<Element> list = document.selectNodes("//mapper");
        for (Element element : list) {
            //SQL映射配置文件路径
            String resource = element.attributeValue("resource");
            //解析SQL映射配置文件
            loadSqlConfig(resource, configuration);
        }
    }


    /**
     * 解析SQL配置文件
     */
    private void loadSqlConfig(String resource, Configuration configuration)
            throws DocumentException {
        //根据SQL映射配置文件路径,读取流资源。classpath路径下
        InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream(resource);
        //创建解析XML文件的对象SAXReader
        SAXReader saxReader = new SAXReader();
        //读取UserMapper.xml配置文件文档对象
        Document document = saxReader.read(inputStream);
        //获取文档对象根节点:<mapper namespace="test">
        Element rootElement = document.getRootElement();
        //取出根节点的命名空间
        String namespace = rootElement.attributeValue("namespace");
        //获取当前SQL映射文件所有查询语句标签
        List<Element> selectNodes = document.selectNodes("//select");
        //循环解析查询标签select,抽取SQL语句
        for (Element element : selectNodes) {
            //查询语句唯一标识
            String id = element.attributeValue("id");
            //当前查询语句返回结果集对象类型
            String resultType = element.attributeValue("resultType");
            //查询语句
            String sql = element.getText();
            //创建Mapper对象
            SqlSource mapper = new SqlSource();
            mapper.setSql(sql);
            mapper.setResultType(resultType);
            //在configuration中设置mapper类,key:(命名空间+.+SQL语句唯一标识符)
            configuration.getSqlSourceMap().put(namespace + "." + id, mapper);
        }
    }
}
SqlSessionFactory
public class SqlSessionFactory {

    private Configuration configuration;
    public SqlSessionFactory(Configuration configuration) {
        this.configuration = configuration;
    }
    /**
     * 创建SqlSession会话
     */
    public SqlSession openSession(){
        return new DefaultSqlSessionImpl(configuration);
    }

    public Configuration getConfiguration() {
        return configuration;
    }
}

SqlSession
public interface SqlSession {

    <T> List<T> selectList(String statement) throws Exception;

    <T> List<T> selectList(String statement, Object parameter) throws Exception;

    int update(String statement) throws Exception;

    int update(String statement, Object parameter) throws Exception;

    int delete(String statement) throws Exception;

    int delete(String statement, Object parameter) throws Exception;

    int insert(String statement) throws Exception;

    int insert(String statement, Object parameter) throws Exception;

    <T> T getMapper(Class<T> type);

}
DefaultSqlSessionImpl
public class DefaultSqlSessionImpl implements SqlSession {
    
    private Configuration configuration;

    public DefaultSqlSessionImpl(Configuration configuration) {
        this.configuration = configuration;
    }

    public <T> List<T> selectList(String statement) throws Exception {
        Executor executor = new Executor(configuration);
        return (List<T>) executor.executeQuery(statement);
    }

    @Override
    public <T> T getMapper(Class<T> type) {
        ClassLoader classLoader = type.getClassLoader();

        // 创建一个代理对象,用户给定一个Mappper接口,我们负责生成一个Mapper代理实例返回去
        // 这样在调用Mapper接口方法的时候,会进入代理类里面。
        InvocationHandler handler = new MapperProxy(configuration);

        return (T) Proxy.newProxyInstance(
                classLoader,
                new Class[]{type},
                handler
        );
    }

    // 其它略

}
Executor
public class Executor {

    private Configuration configuration;

    public Executor(Configuration configuration) {
        this.configuration = configuration;
    }

    public List executeQuery(String statement) throws Exception {
        /**
         * 数据库连接信息硬编码
         */
        String driver = configuration.getDriver();
        String url = configuration.getUrl();
        String username = configuration.getUsername();
        String password = configuration.getPassword();
        Map<String, SqlSource> map = configuration.getSqlSourceMap();

        //SQL映射对象
        SqlSource mapper = map.get(statement);
        String sqlStr = mapper.getSql();
        //获取查询SQL
        String resultType = mapper.getResultType();     //获取返回值类的全限定名(包名        +类名)
        //1.注册MySQL驱动
        Class.forName(driver);
        //2.获取连接Connection对象
        /**
         * 数据库连接的创建和释放频繁,造成系统资源的浪费,严重影响了系统性能
         */
        Connection conn = DriverManager.getConnection(url, username, password);
        //3.创建SQL语句对象Statement,填写SQL语句
        PreparedStatement ps = conn.prepareStatement(sqlStr);
        //4.执行查询SQL,返回结果集ResultSet
        ResultSet rs = ps.executeQuery();
        //5.解析结果集,获取查询用户list集合
        //获取结果集元数据
        ResultSetMetaData metaData = rs.getMetaData();
        //获取总列数
        int columnCount = metaData.getColumnCount();
        //获取所有列的list集合
        List<String> columnNames = new ArrayList<String>();
        for (int i = 1; i <= columnCount; i++) {
            columnNames.add(metaData.getColumnName(i));
        }
        List list = new ArrayList();
        //循环解析结果集
        while (rs.next()) {
            //通过反射获取类的字节码对象,传入的参数是类的全限定名称
            Class<?> clazz = Class.forName(resultType);
            //反射创建对象
            Object user = clazz.newInstance();
            //反射获取当前类的所有方法
            Method[] methods = clazz.getMethods();
            //循环 遍历所有列名
            for (String columnName : columnNames) {
                for (Method method : methods) {
                    String methodName = method.getName();
                    //判断方法的名称,与set+列名相等,那么就把列名对应的值设置到当前对象的set方法中
                    if (("set" + columnName).equalsIgnoreCase(methodName)) {
                        //把列名column对应的值,设置到对象的set方法中,给属性赋值
                        method.invoke(user, rs.getObject(columnName));
                    }
                }
            }
            //将用户存入集合中
            list.add(user);
        }
        //关闭连接,释放资源
        rs.close();
        ps.close();
        conn.close();
        return list;
    }
}

MapperProxy
public class MapperProxy implements InvocationHandler {

    private final Configuration configuration;

    public MapperProxy(Configuration configuration) {
        this.configuration = configuration;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 获取方法所在类的包路径
        String packagePath = method.getDeclaringClass().getName();
        // 获取方法名
        String methodName = method.getName();

        // 将包路径和方法名通过"."拼接在一起
        String fullPath = packagePath + "." + methodName;


        SqlSource sqlSource = configuration.getSqlSourceMap().get(fullPath);

        SqlSessionFactory factory = new SqlSessionFactory(this.configuration);

        SqlSession sqlSession = factory.openSession();

        switch (getSQLStatementType(sqlSource.getSql())) {
            case SELECT:
                // 简单来
                return sqlSession.selectList(fullPath);
            case DELETE: break;
            case UPDATE: break;
            case INSERT: break;


        }
        throw new RuntimeException();
    }

    public enum SQLStatementType {
        SELECT,
        DELETE,
        UPDATE,
        INSERT,
        UNKNOWN
    }


    public static SQLStatementType getSQLStatementType(String sql) {
        String normalizedSQL = sql.trim().toLowerCase();

        if (normalizedSQL.startsWith("select")) {
            return SELECT;
        } else if (normalizedSQL.startsWith("delete")) {
            return SQLStatementType.DELETE;
        } else if (normalizedSQL.startsWith("update")) {
            return SQLStatementType.UPDATE;
        } else if (normalizedSQL.startsWith("insert")) {
            return SQLStatementType.INSERT;
        } else {
            return SQLStatementType.UNKNOWN;
        }
    }


}

使用Demo

按照之前的用例图,我们先导入工程的依赖包、配置数据源等信息、编写Mapper、编写Mapper XML,就可以运行了。

  • 导包
<!--自定义框架坐标jar包-->
<dependency>
    <groupId>cn.lsj</groupId>
    <artifactId>lsjORM</artifactId>
    <version>1.0-SNAPSHOT</version>
</dependency>

<!--mysql数据库驱动-->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.6</version>
</dependency>

<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.13.1</version>
    <scope>compile</scope>
</dependency>
  • SqlMapConfig.xml
<?xml version="1.0" encoding="UTF-8" ?>
<configuration>
    <dataSource>
        <property name="driver" value="com.mysql.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://127.0.0.1:3306/ruoyi?characterEncoding=utf8"/>
        <property name="username" value="root"/>
        <property name="password" value="123456"/>
    </dataSource>
    <mappers>
        <mapper resource="SysRoleMapper.xml"/>
    </mappers>
</configuration>

  • SysRoleMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<mapper namespace="com.hero.dao.SysRoleMapper">
    <select id="findAll" resultType="com.hero.dao.SysRole">
        select * from sys_role;
    </select>
</mapper>
  • 接口和实体
public interface SysRoleMapper {

    List<SysRole> findAll();
}

public class SysRole {

    private Long role_id;

    private String role_name;

    // getting/setting
}
  • 测试类
@Test
public void test() throws Exception {
    //1.创建SqlSessionFactoryBuilder对象
    SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
    //2.builder对象构建工厂对象
    InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream("SqlMapConfig.xml");

    SqlSessionFactory sqlSessionFactory = builder.build(inputStream);
	// 拿到接口的默认实现类DefaultSqlSessionImpl
    SqlSession sqlSession = sqlSessionFactory.openSession();

    // 调用方式一:直接指定方法全路径
    List<SysRole> objects = sqlSession.selectList("com.hero.dao.SysRoleMapper.findAll");
    for (SysRole u : objects) {
        System.out.println(u);
    }

    // 调用方式二:返回Mappper接口代理实例 MapperProxy,findAll方法会跑MapperProxy#invoke()方法
    SysRoleMapper sysRoleMapper = sqlSession.getMapper(SysRoleMapper.class);
    List<SysRole> users = sysRoleMapper.findAll();

    for (SysRole u : users) {
        System.out.println(u);
    }
}

总结

通过手写一个简单的ORM框架,了解到 SQL映射配置、SQL执行、结果映射的基本原理,在上面的案例中,没有实现参数处理、缓存机制、插件机制,感兴趣的可以继续完善。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1300648.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

SpringBoot集成系列--xxlJob

文章目录 一、搭建调度中心xxl-job-admin1、下载项目2、调整项目参数3、执行初始化数据库SQL4、启动项目5、访问 二、集成步骤1、添加xxl-job的依赖2、添加xxl-job的依赖3、配置执行器4、创建执行器5、开发任务1&#xff09;方式1&#xff1a;BEAN模式&#xff08;方法形式&…

文心ERNIE Bot SDK+LangChain:基于文档、网页的个性化问答系统

现在各行各业纷纷选择接入大模型&#xff0c;其中最火且可行性最高的形式无异于智能文档问答助手&#xff0c;而LangChain是其中主流技术实现工具&#xff0c;能够轻松让大语言模型与外部数据相结合&#xff0c;从而构建智能问答系统。ERNIE Bot SDK已接入文心大模型4.0能力&am…

2023-12-10 LeetCode每日一题(爬楼梯)

2023-12-10每日一题 一、题目编号 70. 爬楼梯二、题目链接 点击跳转到题目位置 三、题目描述 假设你正在爬楼梯。需要 n 阶你才能到达楼顶。 每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢&#xff1f; 示例 1&#xff1a; 示例 2&#xff1a; 提…

【vim】常用操作

用的时候看看&#xff0c;记太多也没用&#xff0c;下面都是最常用的&#xff0c;更多去查文档vim指令集。 以下均为正常模式下面操作&#xff0c;正在编辑的&#xff0c;先etc一下. 1/拷贝当前行 yy&#xff0c;5yy为拷贝包含当前行往下五行 2/p将拷贝的东西粘贴到当前行下…

(第一部分1-3 / 13)PMBOK 6

项目管理协会 PMI 价值观 责任、尊重、公正、诚实 法约尔 亨利法约尔_百度百科 管理的5项职能 计划组织指挥协调控制管理的14项原则&#xff08;该原则的提出&#xff0c;标志着法约尔管理理论的形成&#xff09; 【劳动分工】劳动分工是合理使用个人力量和集体力量的最好…

36 括号匹配问题

import java.util.HashMap; import java.util.LinkedList; import java.util.Scanner;public class Main {// 输入获取public static void main(String[] args) {Scanner sc new Scanner(System.in);String s sc.nextLine();System.out.println(getResult(s));}// 算法入口pu…

EdgeYOLO: anchor-free,边缘部署友好

简体中文 1 Intro 2 Updates 3 Coming Soon 4 Models 5 Quick Start \quad 5.1 setup

Cpolar配置外网访问和Dashy

Dashy是一个开源的自托管的导航页配置服务,具有易于使用的可视化编辑器、状态检查、小工具和主题等功能。你可以将自己常用的一些网站聚合起来放在一起,形成自己的导航页。一款功能超强大,颜值爆表的可定制专属导航页工具 结合cpolar内网工具,我们实现无需部署到公网服务器…

详解异常 ! !(对异常有一个全面的认识)

【本章目标】 1. 异常概念与体系结构 2. 异常的处理方式 3. 异常的处理流程 4. 自定义异常类 1. 异常的概念与体系结构 1.1 异常的概念 在生活中&#xff0c;一个人表情痛苦&#xff0c;出于关心&#xff0c;可能会问&#xff1a;你是不是生病了&#xff0c;需要我陪你去看医…

T曲线速度控制与S曲线速度控制

梯形速度控制曲线&#xff08;T曲线&#xff09; 所谓梯形速度曲线&#xff0c;也称为直线加减速或T型加减速。 其算法十分简易&#xff0c;规划周期耗时短&#xff0c;有利于缩减系统的连续运行时间&#xff0c;从而提高系统的运动控制速度&#xff0c;实施起来比较易&#x…

构建linux系统的强有力工具buildroot

目录 01. buildroot简介 1.1简介 1.2 源码下载 1.3 buildroot版本更新介绍 02. 实操演示 2.1 buildroot源码目录层级的简介 2.2 实操演示 03. 结语 01. buildroot简介 1.1简介 Buildroot是一个嵌入式Linux系统的工具链&#xff0c;它用于构建嵌入式设备的根文件系统。…

利用pycharm进行python代码的debug各功能解释

利用pycharm进行python代码的debug各功能解释 利用pycharm进行python代码的debug各功能解释 一 进入debug 1 左键点击代码文件目标行&#xff0c;确定debug节点&#xff08;可一次选择多个debug节点&#xff09; 2 鼠标右键&#xff0c;选择debug 二 各功能解读 debug调试…

第九天:信息打点-CDN绕过篇amp;漏洞回链amp;接口探针amp;全网扫描amp;反向邮件

信息打点-CDN绕过篇 cdn绕过文章&#xff1a;https://www.cnblogs.com/qiudabai/p/9763739.html 一、CDN-知识点 1、常见访问过程 1、没有CDN情况下传统访问&#xff1a;用户访问域名-解析服务器IP–>访问目标主机 2.普通CDN&#xff1a;用户访问域名–>CDN节点–>…

鸿蒙OS应用开发之语句

在程序开发中&#xff0c;已经有上面的运算符和数据类型了&#xff0c;可以满足了大部的需求&#xff0c;但是这些程序还是比较简单的计算和逻辑运算&#xff0c;如果需要复杂的计算和逻辑处理&#xff0c;就需要采用复杂逻辑程序块来处理了&#xff0c;也就是复杂条件语句才能…

Kubernetes里的DNS;API资源对象ingress;Kubernetes调度;节点选择器NodeSelector;节点亲和性NodeAffinity

Kubernetes里的DNS K8s集群内有一个DNS服务&#xff1a; kubectl get svc -n kube-system |grep dns测试&#xff1a; 在tang3上安装bind-utils,目的是安装dig命令 yum install -y bind-utils apt install dnsutils #ubuntu上 解析外网域名 dig 10.15.0.10 www.baidu.com…

C语言笔试例题_指针专练30题(附答案解析)

C语言笔试例题_指针专练30题(附答案解析) 指针一直是C语言的灵魂所在&#xff0c;是掌握C语言的必经之路&#xff0c;收集30道C语言指针题目分享给大家&#xff0c;测试环境位64位ubuntu18.04环境&#xff0c;如有错误&#xff0c;恳请指出&#xff0c;文明讨论&#xff01;&am…

OpenGLES:glReadPixels()获取相机GLSurfaceView预览数据并保存

Android现行的Camera API2机制可以通过onImageAvailable(ImageReader reader)回调从底层获取到Jpeg、Yuv和Raw三种格式的Image&#xff0c;然后通过保存Image实现拍照功能&#xff0c;但是却并没有Api能直接在上层直接拿到实时预览的数据。 Android Camera预览的实现是上层下发…

飞天使-linux操作的一些技巧与知识点

命令行光标移动到行首行尾 ctrl a 跳到首 ctrl e 跳到尾/etc/passwd rpm 包格式 RPM&#xff08;Red Hat Package Manager&#xff09;是一种常用的Linux软件包管理系统&#xff0c;它使用特定的命名规则来标识和命名软件包。RPM包的名称格式通常遵循以下规则&#xff1a;…

angular route guards

它的作用 有5种guard 1.先创建一个services的typescript class 这个sivices 实现了 canActive 这个interface &#xff08;接口&#xff09; 返回true可以继续执行&#xff0c; 返回false中断执行 2. 在app.module的providers中使用这个services 3.最后在路由文件中使用 路…

一张图解释maxHistory、fileNamePattern、totalSizeCap、maxFileSize之间的关系

文中结合配置和坐标轴的关系进行案例讲解&#xff0c;文中知识针对每个案例情况进行解释&#xff0c;当然也存在其他案例&#xff0c;对比理解即可。 配置案例 <rollingPolicy class"ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy"><fileNam…