MyBatis-配置文件解读及MyBatis为何不用编写Mapper接口的实现类

MyBatis核心配置文件详解

多环境

xml 复制代码
<!--一个环境对应一个数据库,开发环境和生产环境可以对应不同的环境
一般一个数据库对应一个sqlSessionFactory对象,一个sqlSessionFactory对象对应一个环境
default表示默认环境-->
<environments default="development">
    <environment id="development">
        <transactionManager type="JDBC"/>
        <dataSource type="POOLED">
            <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
            <property name="url" value="jdbc:mysql://localhost:3306/spring6"/>
            <property name="username" value="root"/>
            <property name="password" value="root"/>
        </dataSource>
    </environment>
    <environment id="mybatisDB">
        <transactionManager type="JDBC"/>
        <dataSource type="POOLED">
            <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
            <property name="url" value="jdbc:mysql://localhost:3306/spring1"/>
            <property name="username" value="root"/>
            <property name="password" value="root"/>
        </dataSource>
    </environment>
</environments>

那么java程序如何使用指定的环境呢?

java 复制代码
public void testEnvironment() throws IOException {
    SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
    // 没指定环境,默认使用mybatis-config.xml中default指定的环境
    SqlSessionFactory sqlSessionFactory = builder.build(Resources.getResourceAsStream("mybatis-config.xml"));
    // 指定环境为development,使用mybatis-config.xml中id为development的环境配置
    SqlSessionFactory sqlSessionFactory2 = builder.build(Resources.getResourceAsStream("mybatis-config.xml"), "development");

}

dataSource数据源

dataSource的作用:为程序提供connection对象(凡是给程序提供connection对象的,都叫做数据源)。

数据源是一套规范,有JDK规定。

dataSource是一个接口。数据库连接池就是一个数据源。

type属性指定数据源类型。

xml 复制代码
<!-- type有三个值可选:POOLED、UNPOOLED和JNDI
            UNPOOLED:表示不使用连接池,每次获取连接都会创建一个新的连接,使用完后需要手动关闭连接,否则会导致资源泄漏
            	适合于小型应用或者测试环境,不适合于生产环境,因为频繁创建和关闭连接会带来性能问题。
            POOLED:使用mybatis自带的连接池,当有请求获取连接时会从连接池中获取一个可用的连接,如果没有可用的连接且连接池
            没有达到最大连接数限制,则会创建一个新的连接并加入连接池;当有请求释放连接时,连接会被返回到连接池中,而不是被关闭。
            JNDI:集成其他第三方的数据库连接池。
                JNDI是一套规范,大部分的web容器都实现了这套规范:比如tomcat、jetty等都实现了JNDI规范,提供了对数据库连接池的支持。
                JNDI是Java命名目录接口。
            type的值不同,下面属性的name可选值也不同,具体可查阅官方文档。
            -->
 <dataSource type="POOLED">
     <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
     <property name="url" value="jdbc:mysql://localhost:3306/spring6"/>
     <property name="username" value="root"/>
     <property name="password" value="root"/>
 </dataSource>

数据库连接池参数

xml 复制代码
  <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/spring6"/>
                <property name="username" value="root"/>
                <property name="password" value="root"/>
                <!--连接池中允许的最大空闲连接数,默认值为5。当连接池中的空闲连接数超过这个值时,连接池会关闭多余的空闲连接,以节省资源。-->
                <property name="poolMaximumActiveConnections" value="8"/>
                <!--连接池中连接被占用的最长时间,默认值为20000毫秒。当连接被占用的时间超过这个值时,连接池会认为这个连接可能已经失效,并将其关闭。
               这个属性可以帮助连接池及时清理失效的连接,避免资源泄漏和性能问题。
               需要注意的是,如果应用程序经常出现连接被占用时间过长的情况,可能是因为数据库操作过于耗时或者连接池的配置不合理,需要进行优化。
-->
                <property name="poolMaximumCheckoutTime" value="10000"/>
               <!-- 每隔2秒打印日志,并且获取连接对象-->
                <property name="poolTimeToWait " value="2000"/>
                <!--最多空闲连接数-->
                <property name="poolMaximumIdleConnections " value="5"/>
            </dataSource>

properties标签

xml 复制代码
 <!--properties是java.util.Properties类,是一个map集合,key和value都是字符串类型,
可以在mybatis-config.xml中定义一些全局的属性,这些属性可以在mybatis-config.xml中的其他地方使用,
也可以在xxxMapper.xml文件中使用。-->
 <properties>
     <property name="key" value="value"/>
     <property name="jdbc.driver" value="com.mysql.cj.jdbc.Driver"/>
     <property name="jdbc.url" value="jdbc:mysql://localhost:3306/spring1"/>
     <property name="jdbc.username" value="root"/>
     <property name="jdbc.password" value="root"/>
 </properties>
  <environments default="development">
        <environment id="mybatisDB">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                    <!--使用${}来取值-->
                <property name="driver" value="${jdbc.driver}"/>
                <property name="url" value="${jdbc.url}"/>
                <property name="username" value="${jdbc.username}"/>
                <property name="password" value="${jdbc.password}"/>
            </dataSource>
        </environment>
    </environments>

properties还有另外一种方式:使用resource属性(推荐)

xml 复制代码
<!-- resource表示从类的根路径下查找资源-->
 <properties resource="jdbc.properties" />

引入jdbc.properties配置文件,其内容如下:

properties 复制代码
jdbc.dirver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mybatis?useSSL=false&serverTimezone=UTC
jdbc.username=root
jdbc.password=123456

mybatis事务控制

一个SqlSession对象控制一个事务。保证一个线程对应一个SqlSession对象。

所以要把SqlSession对象放在ThreadLocal里面。

mybatis三大对象作用域

SqlSessionFactoryBuilder

这个类用来创建SqlSessionFactory对象,它的作用域是方法作用域。只使用一次。

SqlSessionFactory

SqlSessionFactory一旦被创建,就在应用的运行期间一直存在,在应用运行期间不要重复创建多次。它的作用域是应用作用域。可以使用单例模式实现。

SqlSession

一个线程对应一个SqlSession实例。SqlSession的实例不是线程安全的,因此不能被共享。它的作用域是请求或方法作用域。绝对不能将SqlSession实例放在一个类的静态域,甚至一个类的实例变量也不行。

免写Mapper接口的实现类(使用javassist生成类)

javassist的效率高于cglib

javassist用于在内存中自动生成dao层的类。

  1. 先引入依赖

    xml 复制代码
    <dependency>
        <groupId>org.javassist</groupId>
        <artifactId>javassist</artifactId>
        <version>3.29.1-GA</version>
    </dependency>
    1. 编写生成代码:

      java 复制代码
      public void javavassistTest() throws Exception {
          // 1. 获取类池,类池用来生成class
          ClassPool pool = ClassPool.getDefault();
          // 2.制造类
          CtClass ctClass = pool.makeClass("com.ali.dao.CarDao");
          // 3.制造方法
          String methodStr = "public void save() { System.out.println(\"保存车辆\"); }";
          CtMethod ctMethod = CtNewMethod.make(methodStr, ctClass);
          // 4.将方法添加到类中
          ctClass.addMethod(ctMethod);
          // 5.将类写入内存
          ctClass.toClass();
          // 6.类加载
          Class<?> aClass = Class.forName("com.ali.dao.CarDao");
          // 7.创建对象
          Object o = aClass.newInstance();
          // 8.获取方法
          Method save = aClass.getDeclaredMethod("save");
          // 9.执行方法
          save.invoke(o);
      
      }

注意:高版本的jdk(jdk8以上)启动的时候会报错,可以在启动时配置以下参数解决:

--add-opens java.base/java.lang=ALL-UNNAMED

--add-opens java.base/sun.net.util=ALL-UNNAMED

使用javassist动态生成类并实现接口

java 复制代码
public void testGenerateClass() throws Exception {
    // 1. 获取类池,类池用来生成class
    ClassPool pool = ClassPool.getDefault();
    // 2.制造类
    CtClass ctClass = pool.makeClass("com.ali.dao.CarDaoImpl");
    // 3.制造接口
    CtClass ctInterface = pool.makeInterface("com.ali.dao.CarDao");
    // 4.将接口添加到类中,实现接口
    ctClass.addInterface(ctInterface);
    // 5.实现接口中的所有方法
    // 5.1 获取接口所有方法
        CtMethod[] methods = ctInterface.getDeclaredMethods();
    // 5.2 遍历方法,获取方法的签名,并实现方法
    for (CtMethod method : methods) {
        StringBuilder methodStr = new StringBuilder();
        methodStr.append("public ");
        methodStr.append(method.getReturnType().getName());
        methodStr.append(" ");
        methodStr.append(method.getName());
        methodStr.append("(");
        // 获取方法参数列表
        CtClass[] parameterTypes = method.getParameterTypes();
        for (int i = 0; i < parameterTypes.length; i++) {
            methodStr.append(parameterTypes[i].getName()).append(" arg").append(i);
            if (i < parameterTypes.length - 1) {
                methodStr.append(", ");
            }
        }
        methodStr.append(") { System.out.println(\"11111\")");
        // 添加return语句
        if (!method.getReturnType().getName().equals("void")) {
            String simpleName = method.getReturnType().getSimpleName();
            if ("void".equals(simpleName)) {
                
            }else if ("int".equals(simpleName)) {
                methodStr.append("return 0");
            } else if ("String".equals(simpleName)) {
                methodStr.append("return \"你好!\"");
            }
        }
        methodStr.append(" }");
        CtMethod ctMethod = CtNewMethod.make(methodStr.toString(), ctClass);
        // 6.将方法添加到类中
        ctClass.addMethod(ctMethod);
    }
    // 7.在内存中生成类,同时将类加载到jvm中
    Class<?> aClass = ctClass.toClass();
    // 8.创建对象,并将对象转换为接口类型
    CarDao carDao = (CarDao)aClass.newInstance();
    carDao.delete();
}

工具类GenerateDaoProxy

mybatis中不用写实现类,其原理就是利用类似以下的工具类通过反射机制在内存中自动实现了接口的实现类。

注意:XxxxMapper.xml文件中的namespace必须是dao接口的全限定名称;sql语句的id必须是dao接口的方法名。否则动态生成的类会报错!

java 复制代码
// 这个类的作用是生成 DAO 接口的实现类,使用 Javassist 来动态创建代理类
public class GenerateDaoProxy {

    // 生成dao接口的实现类额,返回一个实现了 daoInterface 接口的类的实例
    public static Object generateProxy(SqlSession sqlSession,Class daoInterface) {
        // 创建一个接口的实现类,命名为 daoInterface 的名字加上 "Proxy"
        String proxyClassName = daoInterface.getName() + "Proxy";
        try {
            ClassPool pool = ClassPool.getDefault();
            CtClass ctClass = pool.makeClass(proxyClassName);
            // 让这个新类实现 daoInterface
            ctClass.addInterface(pool.makeInterface(daoInterface.getName()));
            // 为每个方法添加一个简单的实现,
            for (CtMethod method : ctClass.getDeclaredMethods()) {
                StringBuilder methodStr = new StringBuilder();
                methodStr.append("public ");
                methodStr.append(method.getReturnType().getName());
                methodStr.append(" ");
                methodStr.append(method.getName());
                methodStr.append("(");
                // 获取方法参数列表
                CtClass[] parameterTypes = method.getParameterTypes();
                for (int i = 0; i < parameterTypes.length; i++) {
                    methodStr.append(parameterTypes[i].getName()).append(" arg").append(i);
                    if (i < parameterTypes.length - 1) {
                        methodStr.append(", ");
                    }
                }
                methodStr.append(") { ");
                methodStr.append("org.apache.ibatis.session.SqlSession sqlSession = com.ali.utils.SqlSessionUtil.openSqlSession();");
                // 需要知道是什么类型的sql语句
                // mybatis框架规定:sql语句的id不能随便写,namespace必须是接口的全限定名,id必须是接口的方法名
                // 所以可以通过反射获取方法的全限定名和方法名来获取sql语句的id
                String sqlId = daoInterface.getName() + "." + method.getName();
                SqlCommandType sqlCommandType = sqlSession.getConfiguration().getMappedStatement(sqlId).getSqlCommandType();
                if (sqlCommandType == SqlCommandType.INSERT) {

                } else if (sqlCommandType == SqlCommandType.UPDATE) {
                    // 参数名必须是 arg0,因为在上面定义方法的时候,参数名是 arg0,如果参数名不一致,就会报错,找不到参数
                    methodStr.append("return sqlSession.update(\"").append(sqlId).append("\", arg0);");
                } else if (sqlCommandType == SqlCommandType.DELETE) {

                }else if (sqlCommandType == SqlCommandType.SELECT) {
                    String returnName = method.getReturnType().getName();
                    methodStr.append("return ("+returnName+")sqlSession.selectOne(\"").append(sqlId).append("\", arg0);");
                }
                methodStr.append("}");
                CtMethod ctMethod = CtMethod.make(methodStr.toString(), ctClass);
                ctClass.addMethod(ctMethod);
            }

            // 将生成的类加载到 JVM 中,并返回一个实例
            Class proxyClass = ctClass.toClass();
            return proxyClass.newInstance();
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

实际开发中,在mybatis框架中,可以使用SqlSession对象的getMapper方法获取mybatis框架为我们生成的代理类实例:

java 复制代码
// 通过getMapper获取 carDao 对象
private CarDao carDao = SqlSessionUtil.openSqlSession().getMapper(CarDao.class);
相关推荐
后端AI实验室6 小时前
用AI写代码,我差点把漏洞发上线:血泪总结的10个教训
java·ai
程序员清风8 小时前
小红书二面:Spring Boot的单例模式是如何实现的?
java·后端·面试
belhomme8 小时前
(面试题)Redis实现 IP 维度滑动窗口限流实践
java·面试
Be_Better8 小时前
学会与虚拟机对话---ASM
java
开源之眼11 小时前
《github star 加星 Taimili.com 艾米莉 》为什么Java里面,Service 层不直接返回 Result 对象?
java·后端·github
Maori31612 小时前
放弃 SDKMAN!在 Garuda Linux + Fish 环境下的优雅 Java 管理指南
java
用户9083246027312 小时前
Spring AI 1.1.2 + Neo4j:用知识图谱增强 RAG 检索(上篇:图谱构建)
java·spring boot
小王和八蛋12 小时前
DecimalFormat 与 BigDecimal
java·后端
beata12 小时前
Java基础-16:Java内置锁的四种状态及其转换机制详解-从无锁到重量级锁的进化与优化指南
java·后端