手把手带你写一个Mybatis框架

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS PostgreSQL,高可用系列 2核4GB
RDS MySQL Serverless 高可用系列,价值2615元额度,1个月
简介: 手把手带你写一个Mybatis框架

一、前言


在手写mybatis框架之前 ,我们先来思考一下这个问题:为啥要有mybatis框架存在?它是为了解决什么问题的?我们带着这两个问题来开始我们手写mybatis框架之旅。

我们刚开始搞java的时候,貌似都知道用jdbc去连接数据库,那我们来看一下jdbc连接数据库的代码:

public static void main(String[] args) {
    Connection conn = null;
    PreparedStatement ps = null;
    ResultSet rs = null;
    try {
        // 加载数据库驱动
        Class.forName("com.mysql.jdbc.Driver");
        // 通过驱动管理类获取数据库连接
        conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8",
                "root", "root");
        // 定义sql语句 ?标识占位符
        String sql = "select * from user where id = ? aand username = ?";
        // 获取预处理statement
        ps = conn.prepareStatement(sql);
        // 设置参数,第一个参数为sql语句中参数的序号(从1开始),第二个参数为 设置的参数值
        ps.setString(1,  "riemann");
        // 向数据库发出sql执行查询,查询出结果集
        rs = ps.executeQuery();
        // 遍历结果集
        while (rs.next()) {
            int id = rs.getInt("id");
            String username = rs.getString("username");
            // 封装User
            user.setId(id);
            user.setUsername(username);
        }
        System.out.println(user);
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        // 释放资源
        if (rs != null) {
            try {
                rs.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if (ps != null) {
            try {
                ps.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if (conn != null) {
            try {
                conn.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}

相信有一定年限工作年限的小伙伴们,不难发现jdbc操作的问题。


二、JDBC问题分析


  • 数据库连接创建、释放频繁造成系统资源浪费,从而影响性能。
  • sql语句存在硬编码,造成代码不易维护。
  • 使用preparedStatement向占有位符号传参数存在硬编码问题。
  • 对结果解析存在硬编码(查询列名),sql变化导致解析代码变化。


知道了JDBC存在的一些问题,我们就来对症下药找解决方案。


1、问题解决方案

  • 数据库频繁创建连接以及释放资源:连接池
  • sql语句及参数存在硬编码:配置文件
  • 动解析封装返回结果集:反射、内省

这也就解释了前面提的问题,为什么要有mybatis框架的存在?这是因为为了解决JDBC操作存在的这些问题。

好了,下面我们就来针对上面的解决方案来设计一个mybatis框架。

2、自定义框架设计

2.1 客户端

提供核心配置文件

  • SqlMapConfig.xml: 存放数据源配置信息
  • Mapper.xml: sql语句的配置文件信息

2.2 框架端

2.2.1 读取配置文件

读取完以后以流的形式存在,我们不能讲读取到的配置信息以流的形式存放在内存中,不好操作,可以创建JavaBean来存储。


Configuration:存放数据库基本信息和Map<唯一标识,Mapper> 唯一标识:namespace+"."+id。

MappedStatement:sql语句、statement类型、输入参数java类型、输出参数java类型。


2.2.2 解析配置文件

创建SqlSessionFactoryBuilder类:

方法:SqlSessionFactorybuild();

使用dom4j解析配置文件,将解析出来的内容封装到 ConfigurationMappedStatement

创建SqlSessionFactory的实现类DefaultSqlSession

2.2.3 创建SqlSessionFactory

方法:openSession():获取SqlSession接口的实现类实例对象


2.2.4 创建SqlSession接口及实现类:主要封装CRUD方法

方法:

selectList(String statementId, Object... params); // 查询所有

selectOne(String statementId, Object... params); // 查询单个

close(); 释放资源

具体实现:封装JDBC完成对数据库表的查询操作


3、设计模式

构建者模式、工厂模式 、代理模式


三、自定义框架实现


在客户端项目中创建配置文件:

1、创建SqlMapConfig.xml

<configuration>
    <!--数据库配置信息-->
    <dataSource>
        <property name="driverClass" value="com.mysql.jdbc.Driver"></property>
        <property name="jdbcUrl" value="jdbc:mysql:///mybatis"></property>
        <property name="username" value="root"></property>
        <property name="password" value="root"></property>
    </dataSource>
    <!--存放mapper.xml的全路径-->
    <mapper resource="UserMapper.xml"></mapper>
</configuration>

2、创建UserMapper.xml

<mapper namespace="user">
    <!--sql的唯一标识:namespace.id来组成 :statementId-->
    <select id="selectList" resultType="com.riemann.pojo.User">
        select * from user
    </select>
    <!--
        User user = new User();
        user.setId(1);
        user.setUsername("riemann");
    -->
    <select id="selectOne" resultType="com.riemann.pojo.User" parameterType="com.riemann.pojo.User">
        select * from user where id = #{id} and username = #{username}
    </select>
</mapper>

接下来就来框架端编写相应的类:


3、User对象

@Data
public class User {
    private Integer id;
    private String username;
}

4、在父工程中引入依赖

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
    </dependency>
    <dependency>
        <groupId>com.google.guava</groupId>
        <artifactId>guava</artifactId>
        <version>28.2-jre</version>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.18.12</version>
        <scope>provided</scope>
    </dependency>
    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-lang3</artifactId>
        <version>3.9</version>
    </dependency>
</dependencies>

5、在子工程中mybatis-persistence引入依赖

<dependencies>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.19</version>
    </dependency>
    <dependency>
        <groupId>c3p0</groupId>
        <artifactId>c3p0</artifactId>
        <version>0.9.1.2</version>
    </dependency>
    <dependency>
        <groupId>log4j</groupId>
        <artifactId>log4j</artifactId>
        <version>1.2.17</version>
    </dependency>
    <dependency>
        <groupId>dom4j</groupId>
        <artifactId>dom4j</artifactId>
        <version>1.6.1</version>
    </dependency>
    <dependency>
        <groupId>jaxen</groupId>
        <artifactId>jaxen</artifactId>
        <version>1.1.6</version>
    </dependency>
</dependencies>

6、完成框架端中的读取配置文

6.1 写Configuration类

@Data
public class Configuration {
    private DataSource dataSource;
    /**
     * key:statementId
     * value:封装好的MappedStatement
     */
    Map<String, MappedStatement> mappedStatementMap = Maps.newHashMap();

6.2 编写MappedStatement类

@Data
public class MappedStatement {
    // id标识
    private String id;
    // 返回值类型
    private String resultType;
    // 参数值类型
    private String parameterType;
    // sql语句
    private String sql;
}

7、完成框架端中的解析配置文件

7.1 编写Resources类,读取客户端的xml文件

public class Resources {
    /**
     * 根据配置文件的路径,将配置文件加载成字节输入流,存储在内存中。
     * @param path 文件路径
     * @return     字节流
     */
    public static InputStream getResourceAsStream(String path) {
        InputStream resourceAsStream = Resources.class.getClassLoader().getResourceAsStream(path);
        return resourceAsStream;
    }
}

7.2 编写SqlSessionFactoryBuilder类

  • 使用dom4j解析配置文件,将解析出来的内容封装到Configuration
  • 创建SqlSessionFactory对象:工厂类:生产SqlSession:会话对象
public class SqlSessionFactoryBuilder {
    public SqlSessionFactory build(InputStream in) throws DocumentException, PropertyVetoException {
        // 1.使用dom4j解析配置文件,将解析出来的内容封装到Configuration
        XMLConfigBuilder xmlConfigBuilder = new XMLConfigBuilder();
        Configuration configuration = xmlConfigBuilder.parseConfig(in);
        // 2.创建SqlSessionFactory对象:工厂类:生产SqlSession:会话对象
        DefaultSqlSessionFactory defaultSqlSessionFactory = new DefaultSqlSessionFactory(configuration);
        return defaultSqlSessionFactory;
    }
}

7.3 编写XMLConfigBuilder类

public class XMLConfigBuilder {
    private Configuration configuration;
    public XMLConfigBuilder() {
        this.configuration = new Configuration();
    }
    /**
     * 该方法就是使用dom4j对配置文件进行解析,封装成Configuration对象
     * @param in 字节输入流
     * @return   Configuration
     */
    public Configuration parseConfig(InputStream in) throws DocumentException, PropertyVetoException {
        Document document = new SAXReader().read(in);
        // <configuration>
        Element rootElement = document.getRootElement();
        List<Element> list = rootElement.selectNodes("//property");
        Properties properties = new Properties();
        for (Element element : list) {
            String name = element.attributeValue("name");
            String value = element.attributeValue("value");
            properties.setProperty(name, value);
        }
        ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource();
        comboPooledDataSource.setDriverClass(properties.getProperty("driverClass"));
        comboPooledDataSource.setJdbcUrl(properties.getProperty("jdbcUrl"));
        comboPooledDataSource.setUser(properties.getProperty("username"));
        comboPooledDataSource.setPassword(properties.getProperty("password"));
        configuration.setDataSource(comboPooledDataSource);
        // mapper.xml解析:拿到路径--字节输入流--dom4j进行解析
        List<Element> mapperList = rootElement.selectNodes("//mapper");
        for (Element element : mapperList) {
            String mapperPath = element.attributeValue("resource");
            InputStream resourceAsStream = Resources.getResourceAsStream(mapperPath);
            XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(configuration);
            xmlMapperBuilder.parse(resourceAsStream);
        }
        return configuration;
    }
}

7.4 编写XMLMapperBuilder类

public class XMLMapperBuilder {
    private Configuration configuration;
    public XMLMapperBuilder(Configuration configuration) {
        this.configuration = configuration;
    }
    public void parse(InputStream in) throws DocumentException {
        Document document = new SAXReader().read(in);
        Element rootElement = document.getRootElement();
        String namespace = rootElement.attributeValue("namespace");
        List<Element> list = rootElement.selectNodes("//select");
        for (Element element : list) {
            String id = element.attributeValue("id");
            String resultType = element.attributeValue("resultType");
            String parameterType = element.attributeValue("parameterType");
            String sqlText = element.getTextTrim();
            MappedStatement mappedStatement = new MappedStatement();
            mappedStatement.setId(id);
            mappedStatement.setResultType(resultType);
            mappedStatement.setParameterType(parameterType);
            mappedStatement.setSql(sqlText);
            String key = namespace + "." + id;
            configuration.getMappedStatementMap().put(key, mappedStatement);
        }
    }
}

7.5 SqlSessionFactory接口以及DefaultSqlSessionFactory实现类

public interface SqlSessionFactory {
    SqlSession openSession();
}
public class DefaultSqlSessionFactory implements SqlSessionFactory {
    private Configuration configuration;
    public DefaultSqlSessionFactory(Configuration configuration) {
        this.configuration = configuration;
    }
    @Override
    public SqlSession openSession() {
        return new DefaultSqlSession(configuration);
    }
}

7.6SqlSession接口以及DefaultSqlSession实现类

public interface SqlSession {
    /**
     * 查询所有
     * @param statementId sql唯一id
     * @param params      sql有可能十四模糊查询,传可变参数
     * @param <E>         泛型
     * @return            List集合
     */
    <E> List<E> selectList(String statementId, Object... params) throws Exception;
    /**
     * 根据条件查询单个
     * @param statementId sql唯一id
     * @param params      sql有可能十四模糊查询,传可变参数
     * @param <T>         泛型
     * @return            某一对象
     */
    <T> T selectOne(String statementId, Object... params) throws Exception;
}
public class DefaultSqlSession implements SqlSession {
    private Configuration configuration;
    public DefaultSqlSession(Configuration configuration) {
        this.configuration = configuration;
    }
    @Override
    public <E> List<E> selectList(String statementId, Object... params) throws Exception {
        // 将要去完成对SimpleExecutor里的query方法的调用
        SimpleExecutor simpleExecutor = new SimpleExecutor();
        MappedStatement mappedStatement = configuration.getMappedStatementMap().get(statementId);
        List<Object> list = simpleExecutor.query(configuration, mappedStatement, params);
        return (List<E>) list;
    }
    @Override
    public <T> T selectOne(String statementId, Object... params) throws Exception {
        List<Object> objects = selectList(statementId, params);
        if (objects.size() == 1) {
            return (T) objects.get(0);
        } else {
            throw new RuntimeException("查询结果为空或者结果过多!");
        }
    }
}

7.7Executor接口以及SimpleExecutor实现类

public interface Executor {
    <E> List<E> query(Configuration configuration, MappedStatement mappedStatement, Object... params) throws SQLException, ClassNotFoundException, NoSuchFieldException, IllegalAccessException, IntrospectionException, InstantiationException, Exception;
}
public class SimpleExecutor implements Executor {
    @Override
    public <E> List<E> query(Configuration configuration, MappedStatement mappedStatement, Object... params) throws Exception {
        // 1.注册驱动,获取连接
        Connection connection = configuration.getDataSource().getConnection();
        // 2.获取sql语句 : select * from user where id = #{id} and username = #{username}
        //   转换sql语句 : select * from user where id = ? and username = ?
        //   转换的过程中 : 还要对#{}里面的值进行解析存储
        String sql = mappedStatement.getSql();
        BoundSql boundSql = getBoundSql(sql);
        // 3.获取预处理对象:preparedStatement
        PreparedStatement preparedStatement = connection.prepareStatement(boundSql.getSqlText());
        // 4.设置参数
        //   获取到了参数的全路径
        String parameterType = mappedStatement.getParameterType();
        Class<?> parameterTypeClass = getClassType(parameterType);
        List<ParameterMapping> parameterMappingList = boundSql.getParameterMappingList();
        for (int i = 0; i < parameterMappingList.size(); i++) {
            ParameterMapping parameterMapping = parameterMappingList.get(i);
            String content = parameterMapping.getContent();
            // 反射
            Field declaredField = parameterTypeClass.getDeclaredField(content);
            // 暴力访问,防止访问的字段是private修饰
            declaredField.setAccessible(true);
            Object obj = declaredField.get(params[0]);
            preparedStatement.setObject(i + 1, obj);
        }
        // 5.执行sql
        ResultSet resultSet = preparedStatement.executeQuery();
        String resultType = mappedStatement.getResultType();
        Class<?> resultTypeClass = getClassType(resultType);
        Object o = resultTypeClass.newInstance();
        List<Object> objects = Lists.newArrayList();
        // 6.封装返回结果集
        while (resultSet.next()) {
            // 元数据
            ResultSetMetaData metaData = resultSet.getMetaData();
            for (int i = 1; i <= metaData.getColumnCount(); i++) {
                // 字段名
                String columnName = metaData.getColumnName(i);
                // 字段的值
                Object value = resultSet.getObject(columnName);
                // 使用反射或者内省,根据数据库表和实体的对应关系,完成封装
                PropertyDescriptor propertyDescriptor = new PropertyDescriptor(columnName, resultTypeClass);
                Method writeMethod = propertyDescriptor.getWriteMethod();
                writeMethod.invoke(o, value);
            }
            objects.add(o);
        }
        return (List<E>) objects;
    }
    private Class<?> getClassType(String parameterType) throws ClassNotFoundException {
        if (parameterType != null) {
            Class<?> clazz = Class.forName(parameterType);
            return clazz;
        }
        return null;
    }
    /**
     * 完成对#{}的解析工作:
     * 1.将#{}使用?进行代替
     * 2.解析出#{}里面的值进行存储
     * @param sql 原生sql
     * @return    解析后的sql
     */
    private BoundSql getBoundSql(String sql) {
        // 1.标记处理类:配置标记解析器来完成对占位符的解析处理工作
        ParameterMappingTokenHandler parameterMappingTokenHandler = new ParameterMappingTokenHandler();
        GenericTokenParser genericTokenParser = new GenericTokenParser("#{", "}", parameterMappingTokenHandler);
        // 2.解析出来的sql
        String parseSql = genericTokenParser.parse(sql);
        // 3.#{}里面解析出来的参数名称
        List<ParameterMapping> parameterMappings = parameterMappingTokenHandler.getParameterMappings();
        BoundSql boundSql = new BoundSql(parseSql, parameterMappings);
        return boundSql;
    }
}

四、自定义框架优化


前面我们提到的问题都解决完了,但是我们继续分析刚刚完成的自定义框架代码,还没有什么问题?

问题如下:

  • dao的实现类中存在重复代码,整个操作的过程模板重复(创建SqlSession、调用SqlSession方法、关闭SqlSession )。
  • dao的实现类存在硬编码,调用SqlSession的方法时,参数statementId硬编码。


解决思路:

使用代理模式生成dao层接口的代理对象!!!


SqlSession 接口中增加getMapper方法

/**
 * 为Dao层接口生成代理实现类
 * @param mapperClass 字节码
 * @param <T>         泛型
 * @return            某一对象
 */
<T> T getMapper(Class<?> mapperClass) throws Exception;

DefaultSqlSession 实现类

@Override
public <T> T getMapper(Class<?> mapperClass) throws Exception {
    // 使用JDK动态代理来为Dao层接口生成代理对象,并返回。
    Object proxyInstance = Proxy.newProxyInstance(mapperClass.getClassLoader(), new Class[]{mapperClass}, new InvocationHandler() {
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            /**
             * 底层都还是去执行JDBC代码
             * 根据不同情况来调用findAll或者findByCondition方法
             * 准备参数:
             * 1.statementId: sql语句的唯一标识 nnamespace.id = 接口全限定名.方法名
             */
            // 方法名
            String methodNme = method.getName();
            String className = method.getDeclaringClass().getName();
            String statementId = className + "." + methodNme;
            // 准备参数 2.params:args
            // 获取被调用方法的返回值类型
            Type genericReturnType = method.getGenericReturnType();
            // 判断是否进行了泛型类型参数化
            if (genericReturnType instanceof ParameterizedType) {
                List<Object> objects = selectList(statementId, args);
                return objects;
            }
            return selectOne(statementId, args);
        }
    });
    return (T) proxyInstance;
}

代理模式的过程如下图:

invoke()方法中的三个参数:

  • Object proxy 当前代理对象的引用
  • Method method 当前被调用方法的的引用
  • Object[] args 传递的参数

最后我们来测试一下优化后的结果:

public class PersistenceTest {
    @Test
    public void test() throws Exception {
        InputStream resourceAsStream = Resources.getResourceAsStream("SqlMapConfig.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
        SqlSession sqlSession = sqlSessionFactory.openSession();
        // 调用
        User user = new User();
        user.setId(1);
        user.setUsername("riemann");
//      User user2 = sqlSession.selectOne("user.selectOne", user);
//      System.out.println(user2);
        UserDao userDao = sqlSession.getMapper(UserDao.class);
        User user2 = userDao.findByCondition(user);
        System.out.println(user2);
    }
}

日志打印如下:

User(id=1, username=riemann)

单个对象的OK,我们再来测一下多个的对象的。


List<User> all = userDao.findAll();
System.out.println(all);

日志打印如下:


[User(id=3, username=edgar), User(id=3, username=edgar), User(id=3, username=edgar)]

什么鬼?多个对象的结果输出显示都是数据库中最后的那条记录。

分析问题:

断点跟踪发现,原来是SimpleExecutor这个类的query方法中有点小问题:


解决完上面以后,测试结果如下:

[User(id=1, username=riemann), User(id=2, username=andy), User(id=3, username=edgar)]

结果正确!!!这下我们就优化好了使用代理模式来生成dao层接口的代理对象,解决了dao的实现类中存在重复代码以及dao的实现类存在硬编码。

五、代码仓库


你居然看完了!!!那必须附上仓库完整代码地址。


https://github.com/riemannChow/perseverance/tree/master/handwriting-framework/mybatis


欢迎小伙伴们关注我的公众号,Java后端主流技术栈的原理、源码分析、架构以及各种互联网高并发、高性能、高可用的解决方案。


喜欢的话,点赞、再看、分享三连。

相关实践学习
每个IT人都想学的“Web应用上云经典架构”实战
本实验从Web应用上云这个最基本的、最普遍的需求出发,帮助IT从业者们通过“阿里云Web应用上云解决方案”,了解一个企业级Web应用上云的常见架构,了解如何构建一个高可用、可扩展的企业级应用架构。
MySQL数据库入门学习
本课程通过最流行的开源数据库MySQL带你了解数据库的世界。 &nbsp; 相关的阿里云产品:云数据库RDS MySQL 版 阿里云关系型数据库RDS(Relational Database Service)是一种稳定可靠、可弹性伸缩的在线数据库服务,提供容灾、备份、恢复、迁移等方面的全套解决方案,彻底解决数据库运维的烦恼。 了解产品详情:&nbsp;https://www.aliyun.com/product/rds/mysql&nbsp;
相关文章
SQL XML Java
82 0
|
2月前
|
SQL Java 数据库连接
区分iBatis与MyBatis:两个Java数据库框架的比较
总结起来:虽然从技术角度看,iBATIS已经停止更新但仍然可用;然而考虑到长期项目健康度及未来可能需求变化情况下MYBATISS无疑会是一个更佳选择因其具备良好生命周期管理机制同时也因为社区力量背书确保问题修复新特征添加速度快捷有效.
189 12
|
3月前
|
SQL XML Java
MyBatis框架如何处理字符串相等的判断条件。
总的来说,MyBatis框架提供了灵活而强大的机制来处理SQL语句中的字符串相等判断条件。无论是简单的等值判断,还是复杂的条件逻辑,MyBatis都能通过其标签和属性来实现,使得动态SQL的编写既安全又高效。
267 0
|
8月前
|
Oracle 关系型数据库 Java
|
8月前
|
SQL 缓存 Java
框架源码私享笔记(02)Mybatis核心框架原理 | 一条SQL透析核心组件功能特性
本文详细解构了MyBatis的工作机制,包括解析配置、创建连接、执行SQL、结果封装和关闭连接等步骤。文章还介绍了MyBatis的五大核心功能特性:支持动态SQL、缓存机制(一级和二级缓存)、插件扩展、延迟加载和SQL注解,帮助读者深入了解其高效灵活的设计理念。
|
10月前
|
SQL Java 数据库连接
对Spring、SpringMVC、MyBatis框架的介绍与解释
Spring 框架提供了全面的基础设施支持,Spring MVC 专注于 Web 层的开发,而 MyBatis 则是一个高效的持久层框架。这三个框架结合使用,可以显著提升 Java 企业级应用的开发效率和质量。通过理解它们的核心特性和使用方法,开发者可以更好地构建和维护复杂的应用程序。
537 29
|
SQL Java 数据库连接
持久层框架MyBatisPlus
持久层框架MyBatisPlus
271 1
持久层框架MyBatisPlus
|
缓存 Cloud Native 安全
探索阿里巴巴新型ORM框架:超越MybatisPlus?
【10月更文挑战第9天】在Java开发领域,Mybatis及其增强工具MybatisPlus长期占据着ORM(对象关系映射)技术的主导地位。然而,随着技术的发展,阿里巴巴集团推出了一种新型ORM框架,旨在提供更高效、更简洁的开发体验。本文将对这一新型ORM框架进行探索,分析其特性,并与MybatisPlus进行比较。
522 0
|
SQL Java 数据库连接
【Java 第十三篇章】MyBatis 框架介绍
MyBatis 原名 iBATIS,2001 年由 Clinton Begin 创建,以其简易灵活著称。2010 年更名以重塑品牌形象。MyBatis 通过 SQL 映射文件将 SQL 语句与 Java 代码分离,支持编写原生 SQL 并与方法映射。具备对象关系映射功能,简化数据库记录处理。支持动态 SQL 构建,灵活应对不同查询条件。内置缓存机制,提升查询效率。相比全功能 ORM,MyBatis 提供更高 SQL 控制度和更好的维护性,并易于与 Spring 等框架集成,广泛应用于 Java 数据访问层。
252 0
|
Java 数据库连接 Spring
后端框架入门超详细 三部曲 Spring 、SpringMVC、Mybatis、SSM框架整合案例 【爆肝整理五万字】
文章是关于Spring、SpringMVC、Mybatis三个后端框架的超详细入门教程,包括基础知识讲解、代码案例及SSM框架整合的实战应用,旨在帮助读者全面理解并掌握这些框架的使用。
后端框架入门超详细 三部曲 Spring 、SpringMVC、Mybatis、SSM框架整合案例 【爆肝整理五万字】