手写乞丐版-Mybatis

2024-04-02 22:08
文章标签 mybatis 手写 乞丐

本文主要是介绍手写乞丐版-Mybatis,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

手写乞丐版Mybatis

需求背景:

​ 针对传统的JDBC编程的痛点:

​ 1、硬编码 针对数据库连接、以及sql 会写在代码中,不利于后期维护

​ 2、频繁的数据库连接,会造成资源浪费,影响系统性能

​ 3、查询条件占位符存在硬编码,不利于维护

​ 4、对于结果的解析,如果需要修改sql结果,也需要进行相应的代码修改

如何结果:

​ 1、数据库连接、sql全部采用配置文件的方式进行可配置化

​ 2、使用数据库连接池

​ 3、将查询条件以及结果使用反射,自动映射字段。

框架设计

使用端:

1、配置sqlMapConfig.xml 文件 :内容包括数据库配置信息、并引入Mapper.xml文件

2、配置Mapper.xml 文件: 内容包括sql语句

框架层:

  1. 读取配置文件,可以将配置文件映射为下面的类

    • Configuration类:核心配置类,存放数据库信息,以及MappedStatement映射

    • MappedStatement类: 包括sql,namespace,入参类型,出参类型

  2. 解析配置文件

    创建sqlSessionFactoryBuilder类

    ​ 使用sqlSessionFactory.build():

    ​ (1) 使用dom4j 解析xml文件,将内容封装到Configuration和MapperedStatement中

    (2) 创建sqlSessionFactrory的实现类 DefaultSqlSession

  3. 创建sqlSessionFactory:

    ​ 创建方法:openSession() 获取到sqlSession接口的实现类实例对象

  4. 创建sqlSession接口及实现类:主要封装crud方法

    ​ 方法:selectList(String statementId,Object param):
    ​ 查询所有 selectOne(String statementId,Object param):

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

具体实现:

创建一个Maven工程:

使用端:

配置sqlMapConfig.xml
<?xml version="1.0" encoding="UTF-8" ?>
<configuration><!-- 数据库配置信息 --><property name="driverClass" value="com.mysql.jdbc.Driver"></property><property name="url" value="jdbc:mysql://127.0.0.1:3306/test?serverTimezone=Asia/Shanghai"></property><property name="username" value="root"></property><property name="password" value="root"></property><!-- mapper配置 --><mapper resource="mapper.xml"></mapper>
</configuration>
配置mapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<mapper namespace="test"><select id="selectoneuser" paramterType="com.xugw.pojo.User" resultType="com.xugw.pojo.User">select * from userwhere id = ${id}and username = ${username}</select><select id="selectalluser" resultType="com.xugw.pojo.User">select * from user</select>
</mapper>
新建实体类
public class User {private int id;private String name;public int getId() {return id;}public void setId(int id) {this.id = id;}public String getName() {return name;}public void setName(String name) {this.name = name;}@Overridepublic String toString() {return "User{" +"id=" + id +", name='" + name + '\'' +'}';}
}

使用端编写完毕


自定义Mybatis框架

新建一个module

xml文件解析

解析前我们先把两个解析对应的对象类创建下:

public class Configuration {// 数据源private DataSource dataSource;// 记录 namespace + id 与 对应sql 的关系private Map<String,MapperedStatement> mapperedStatementMap = new HashMap<String,MapperedStatement>();public DataSource getDataSource() {return dataSource;}public void setDataSource(DataSource dataSource) {this.dataSource = dataSource;}public Map<String, MapperedStatement> getMapperedStatementMap() {return mapperedStatementMap;}public void setMapperedStatementMap(Map<String, MapperedStatement> mapperedStatementMap) {this.mapperedStatementMap = mapperedStatementMap;}
}public class MapperedStatement {// namespaceidprivate String id;// 解析后的sql内容private String sql;// 入参实体类private Class<?> paramterType;// 结果实体类private Class<?> resultType;public String getId() {return id;}public void setId(String id) {this.id = id;}public String getSql() {return sql;}public void setSql(String sql) {this.sql = sql;}public Class<?> getParamterType() {return paramterType;}public void setParamterType(Class<?> paramterType) {this.paramterType = paramterType;}public Class<?> getResultType() {return resultType;}public void setResultType(Class<?> resultType) {this.resultType = resultType;}
}

创建完成后:

第一步:读取xml文件
public class Resource {public static InputStream getResourceAsStream(String path){return Resource.class.getClassLoader().getResourceAsStream(path);}
}
第二步:解析配置文件
// 对 sqlMapConfig.xml 解析 同时因为我们在这个xml中已经配置了 Mapper.xml 对应的路径,也可以一起对 mapper.xml 进行解析
public class XMLMapperBuilder {private Configuration configuration ;public XMLMapperBuilder(Configuration configuration) {this.configuration = configuration;}/*** 解析 mapper.xml* @param resourceAsStream*/public void parse(InputStream resourceAsStream) throws DocumentException, ClassNotFoundException {Document read = new SAXReader().read(resourceAsStream);Element rootElement = read.getRootElement();// mapper的namespaceString namespace = rootElement.attributeValue("namespace");// 获取selectList<Element> selectlists = rootElement.selectNodes("select");installconfiguration(selectlists,namespace);List<Element> updatelists = rootElement.selectNodes("update");installconfiguration(updatelists,namespace);}/*** 组装 mapperedStatement* @param namespace*/private void installconfiguration(List<Element> elements, String namespace) throws ClassNotFoundException {for (Element element : elements) {String id = element.attributeValue("id");String resultType = element.attributeValue("resultType");String paramterType = element.attributeValue("paramterType");String sqlText = element.getTextTrim();MapperedStatement mappedStatement = new MapperedStatement();mappedStatement.setId(id);mappedStatement.setResultType(getClassType(resultType));mappedStatement.setParamterType(getClassType(paramterType));mappedStatement.setSql(sqlText);configuration.getMapperedStatementMap().put(namespace+"."+id,mappedStatement);}}private Class<?> getClassType(String resultType) throws ClassNotFoundException {return Class.forName(resultType);}
}public class XMLConfigerBuilder {private Configuration configuration;public XMLConfigerBuilder(Configuration configuration) {this.configuration = new Configuration();}// 解析public Configuration parse(InputStream inputStream) throws DocumentException, PropertyVetoException, ClassNotFoundException {Document read = new SAXReader().read(inputStream);Element rootElement = read.getRootElement();// 数据库连接信息List<Element> configlist = rootElement.selectNodes("//property");Properties properties = new Properties();for (Element property :configlist) {properties.setProperty(property.attributeValue("name"), property.attributeValue("value"));}// 连接池ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource();comboPooledDataSource.setDriverClass(properties.getProperty("driverClass"));comboPooledDataSource.setJdbcUrl(properties.getProperty("url"));comboPooledDataSource.setUser(properties.getProperty("username"));comboPooledDataSource.setPassword(properties.getProperty("password"));// 填充configuration.setDataSource(comboPooledDataSource);// 解析mapper.xmlList<Element> mappers = rootElement.selectNodes("//mapper");XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(configuration) ;for (Element mapper:mappers) {String resource = mapper.attributeValue("resource");InputStream resourceAsStream = Resource.getResourceAsStream(resource);xmlMapperBuilder.parse(resourceAsStream);}return configuration;}}

已上我们已经将xml文件的信息放到配置类中了

sql底层执行

​ 这里我们提取了一个Executor ,这个Executor是实际上真正执行sql的

​ 在Executor中,我们需要对 mapperedstatement中的内容进行处理,包括

​ sql占位符替换,入参和出参的映射

sql占位符替换完成后生成BoundSql

public class BoundSql {private String sqlText;private List<ParameterMapping> parameterMappings = new ArrayList<>();public BoundSql(String sqlText, List<ParameterMapping> parameterMappings) {this.sqlText = sqlText;this.parameterMappings = parameterMappings;}public String getSqlText() {return sqlText;}public void setSqlText(String sqlText) {this.sqlText = sqlText;}public List<ParameterMapping> getParameterMappings() {return parameterMappings;}public void setParameterMappings(List<ParameterMapping> parameterMappings) {this.parameterMappings = parameterMappings;}
}

这里需要一个工具包,从mybatis源码中薅下来的:

/***    Copyright 2009-2017 the original author or authors.**    Licensed under the Apache License, Version 2.0 (the "License");*    you may not use this file except in compliance with the License.*    You may obtain a copy of the License at**       http://www.apache.org/licenses/LICENSE-2.0**    Unless required by applicable law or agreed to in writing, software*    distributed under the License is distributed on an "AS IS" BASIS,*    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.*    See the License for the specific language governing permissions and*    limitations under the License.*//*** @author Clinton Begin*/
public class GenericTokenParser {private final String openToken; //开始标记private final String closeToken; //结束标记private final TokenHandler handler; //标记处理器public GenericTokenParser(String openToken, String closeToken, TokenHandler handler) {this.openToken = openToken;this.closeToken = closeToken;this.handler = handler;}/*** 解析${}和#{}* @param text* @return* 该方法主要实现了配置文件、脚本等片段中占位符的解析、处理工作,并返回最终需要的数据。* 其中,解析工作由该方法完成,处理工作是由处理器handler的handleToken()方法来实现*/public String parse(String text) {// 验证参数问题,如果是null,就返回空字符串。if (text == null || text.isEmpty()) {return "";}// 下面继续验证是否包含开始标签,如果不包含,默认不是占位符,直接原样返回即可,否则继续执行。int start = text.indexOf(openToken, 0);if (start == -1) {return text;}// 把text转成字符数组src,并且定义默认偏移量offset=0、存储最终需要返回字符串的变量builder,// text变量中占位符对应的变量名expression。判断start是否大于-1(即text中是否存在openToken),如果存在就执行下面代码char[] src = text.toCharArray();int offset = 0;final StringBuilder builder = new StringBuilder();StringBuilder expression = null;while (start > -1) {// 判断如果开始标记前如果有转义字符,就不作为openToken进行处理,否则继续处理if (start > 0 && src[start - 1] == '\\') {builder.append(src, offset, start - offset - 1).append(openToken);offset = start + openToken.length();} else {//重置expression变量,避免空指针或者老数据干扰。if (expression == null) {expression = new StringBuilder();} else {expression.setLength(0);}builder.append(src, offset, start - offset);offset = start + openToken.length();int end = text.indexOf(closeToken, offset);while (end > -1) {存在结束标记时if (end > offset && src[end - 1] == '\\') {//如果结束标记前面有转义字符时// this close token is escaped. remove the backslash and continue.expression.append(src, offset, end - offset - 1).append(closeToken);offset = end + closeToken.length();end = text.indexOf(closeToken, offset);} else {//不存在转义字符,即需要作为参数进行处理expression.append(src, offset, end - offset);offset = end + closeToken.length();break;}}if (end == -1) {// close token was not found.builder.append(src, start, src.length - start);offset = src.length;} else {//首先根据参数的key(即expression)进行参数处理,返回?作为占位符builder.append(handler.handleToken(expression.toString()));offset = end + closeToken.length();}}start = text.indexOf(openToken, offset);}if (offset < src.length) {builder.append(src, offset, src.length - offset);}return builder.toString();}
}public class ParameterMapping {private String content;public ParameterMapping(String content) {this.content = content;}public String getContent() {return content;}public void setContent(String content) {this.content = content;}
}public class ParameterMappingTokenHandler implements TokenHandler {private List<ParameterMapping> parameterMappings = new ArrayList<ParameterMapping>();// context是参数名称 #{id} #{username}// 进了List 最终生成sql时会按照顺序 进行值传递public String handleToken(String content) {parameterMappings.add(buildParameterMapping(content));return "?";}private ParameterMapping buildParameterMapping(String content) {ParameterMapping parameterMapping = new ParameterMapping(content);return parameterMapping;}public List<ParameterMapping> getParameterMappings() {return parameterMappings;}public void setParameterMappings(List<ParameterMapping> parameterMappings) {this.parameterMappings = parameterMappings;}}/*** @author Clinton Begin*/
public interface TokenHandler {String handleToken(String content);
}
 public interface Executor {/*** 针对查询* @param <E>* @param configuration* @param mapperedStatement* @param params* @return* @throws Exception*/public <E> List<E> query(Configuration configuration, MapperedStatement mapperedStatement,Object... params) throws Exception;/*** 针对不需要返回结果,比如 update/insert/delete等* @throws Exception*/public void execute(Configuration configuration, MapperedStatement mapperedStatement, Object... params) throws Exception;}public class SimpleExecutor implements Executor{@Overridepublic <E> List<E> query(Configuration configuration,MapperedStatement mapperedStatement, Object... params) throws Exception {Class<?> paramterType = mapperedStatement.getParamterType();String sql = mapperedStatement.getSql();BoundSql boundSql = getBoundSql(sql);List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();ArrayList<Object> objects = new ArrayList<>();try {// 遍历java集合Connection connection = configuration.getDataSource().getConnection();PreparedStatement preparedStatement = connection.prepareStatement(boundSql.getSqlText());if (paramterType != null) {for (int i = 0; i < parameterMappings.size(); i++) {String content = parameterMappings.get(i).getContent();//反射//获取paraterType的全路径 ;Field declaredField = paramterType.getDeclaredField(content);// 暴力访问declaredField.setAccessible(true);Object o = declaredField.get(params[0]);preparedStatement.setObject(i + 1, o);}}// 执行sqlResultSet resultSet = preparedStatement.executeQuery();Class<?> resultTypeClass = mapperedStatement.getResultType();// 封装返回结果集while (resultSet.next()) {Object o = null;//TODO Map类型o = resultTypeClass.newInstance();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.toLowerCase(), resultTypeClass);Method writeMethod = propertyDescriptor.getWriteMethod();writeMethod.invoke(o, value);}objects.add(o);}// 释放resultSet.close();preparedStatement.close();} catch (SQLException throwables) {throwables.printStackTrace();} catch (IntrospectionException e) {e.printStackTrace();} catch (NoSuchFieldException e) {e.printStackTrace();} catch (InvocationTargetException e) {e.printStackTrace();} catch (IllegalAccessException e) {e.printStackTrace();} catch (InstantiationException e) {e.printStackTrace();}return (List<E>) objects;}@Overridepublic void execute(Configuration configuration, MapperedStatement mapperedStatement, Object... params) throws Exception {Class<?> paramterType = mapperedStatement.getParamterType();String sql = mapperedStatement.getSql();// 特殊处理 资源需要根据本地网 获取不同的数据库名BoundSql boundSql = null;boundSql = getBoundSql(sql);List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();try {// 遍历java集合Connection connection = configuration.getDataSource().getConnection();PreparedStatement preparedStatement = connection.prepareStatement(boundSql.getSqlText());if (paramterType != null) {for (int i = 0; i < parameterMappings.size(); i++) {String content = parameterMappings.get(i).getContent();//反射//获取paraterType的全路径 ;Class<?> paratertypeClass = paramterType;Field declaredField = paratertypeClass.getDeclaredField(content);// 暴力访问declaredField.setAccessible(true);Object o = declaredField.get(params[0]);preparedStatement.setObject(i + 1, o);}}preparedStatement.execute();preparedStatement.close();} catch (SQLException throwables) {throwables.printStackTrace();} catch (NoSuchFieldException e) {e.printStackTrace();} catch (IllegalAccessException e) {e.printStackTrace();}}private BoundSql getBoundSql(String sql, String... params) {// 标记处理类ParameterMappingTokenHandler parameterMappingTokenHandler = new ParameterMappingTokenHandler();// 标记解析器GenericTokenParser genericTokenParser = new GenericTokenParser("${", "}", parameterMappingTokenHandler);// 解析过后的sqlString parsesql = genericTokenParser.parse(sql);// ${} 解析出来的参数名称List<ParameterMapping> parameterMappings = parameterMappingTokenHandler.getParameterMappings();BoundSql boundSql = new BoundSql(parsesql, parameterMappings);return boundSql;}}

到这边 实际上我们的核心已经写完了,下面就是把 业务代码和 执行代码结合

SqlSession创建

我们创建下数据查询的接口和它的实现类:

public interface SqlSession {public <E> List<E> selectList(String statementId, Object... params) throws Exception;public <T> T selectOne(String statementId,Object... params) throws Exception;
}public class DefaultSqlSession implements SqlSession {private Configuration configuration;private SimpleExecutor executor;public DefaultSqlSession(Configuration configuration) {this.configuration = configuration;executor = new SimpleExecutor();}@Overridepublic <E> List<E> selectList(String statementId, Object... params) throws Exception {return executor.query(configuration,configuration.getMapperedStatementMap().get(statementId));}@Overridepublic <T> T selectOne(String statementId, Object... params) throws Exception {List<Object> results = executor.query(configuration, configuration.getMapperedStatementMap().get(statementId));if(results.size() == 1){return (T) results.get(0);}else {throw new Exception("返回结果过多");}}
}

我们采用工厂+建造者模式进行创建

SqlSessionFactory创建
public interface SqlSessionFactory {public SqlSession openSession();
}public class DefaultSqlSessionFactory implements SqlSessionFactory {private Configuration configuration;public DefaultSqlSessionFactory(Configuration configuration) {this.configuration = configuration;}@Overridepublic SqlSession openSession() {return new DefaultSqlSession(configuration);}
}
SqlSessionFactoryBuilder创建
public class SqlSessionFactoryBuilder {private Configuration configuration;public SqlSessionFactoryBuilder() {this.configuration = new Configuration();}public SqlSessionFactory build(InputStream inputStream) throws PropertyVetoException, DocumentException, ClassNotFoundException {XMLConfigerBuilder xmlConfigerBuilder = new XMLConfigerBuilder(configuration);// 解析配置文件Configuration configuration = xmlConfigerBuilder.parse(inputStream);// 创建sqlSessionFactorySqlSessionFactory sqlSessionFactory = new DefaultSqlSessionFactory(configuration);return sqlSessionFactory;}}

框架部分编写完成

数据验证:

​ 我们在使用端新建一个测试类:

public class TestMybatis {@Testpublic void test() throws Exception {InputStream resourceAsStream = Resource.getResourceAsStream("sqlMapConfig.xml");SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);SqlSession sqlSession = sqlSessionFactory.openSession();List<User> users = sqlSession.selectList("test.selectalluser");for (User user :users) {System.out.println(user.toString());}}
}

执行后

在这里插入图片描述


反思进化:

上面已经完成了核心方法的编写,但是我们发现,测试类中使用的是sqlSession.selectList(mapperstatemetnId)的形式,与我们平常使用的好像有点不一样。平时我们好像只需要对应下 mapper接口 与 实际mapper.xml的位置,然后就可以直接使用了。

同时上述方法也存在硬编码的问题,与我们初衷背离。

那么这是怎么实现的呢?我们能不能直接通过mapper接口直接进行数据操作呢?

=>

我们可以使用代理模式,对UserMapper 生成代理,并进行方法增强,这样我们就可以完成上述事情。

通过代理,我们可以直接取得Mapper接口类的全限定名,然后从configuration 中取到对应的数据信息。这也是为什么我们在mybatis里面 要求namespace 以及 id 要对应类、方法名的原因

sqlSession改造:

我们在sqlSession中新建一个getMapper.xml方法

public interface SqlSession {public <E> List<E> selectList(String statementId, Object... params) throws Exception;public <T> T selectOne(String statementId,Object... params) throws Exception;public Object update(String statementId, Object... params) throws Exception;public <T> T getMapper(Class<?> mapperclass);
}
@Overridepublic <T> T getMapper(Class<?> mapperclass) {// 使用Jdk动态代理生成代理对象Object o = Proxy.newProxyInstance(mapperclass.getClassLoader(), new Class[]{mapperclass}, new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {// 底层还是执行jdbc 根据不同情况 调用selectList / selectOne// 获取statementid = 接口全限定名.方法名// 方法名String methodName = method.getName();String className = method.getDeclaringClass().getName();String statementId = className + "." + methodName;// 获取参数 args// 获取被调用方法的返回值类型Type genericReturnType = method.getGenericReturnType();// 根据sql 字符串的第一个单词 判断操作类型String[] s = configuration.getMapperedStatementMap().get(statementId).getSql().split(" ");// 同一大写switch (s[0].toUpperCase()) {case "SELECT":// 判断类型 是否泛型参数化if (genericReturnType instanceof ParameterizedType) {List<Object> objects = selectList(statementId, args);return objects;}return selectOne(statementId, args);case "INSERT":case "UPDATE":case "DELETE":return update(statementId, args);default:throw new Exception("找不到对应的执行方法");}}});return (T)o;}

数据验证:

新建一个Mapper接口

package com.xugw.com.xugw.mapper;import com.xugw.pojo.User;import java.util.List;/*** @author xugw* @description*/
public interface UserMapper {List<User> selectAllUser();
}

修改对应的mapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<mapper namespace="com.xugw.com.xugw.mapper.UserMapper"><select id="selectAllUser" paramterType="com.xugw.pojo.User" resultType="com.xugw.pojo.User">select * from user</select>
</mapper>

测试类:

@Test
public void test() throws Exception {InputStream resourceAsStream = Resource.getResourceAsStream("sqlMapConfig.xml");SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);SqlSession sqlSession = sqlSessionFactory.openSession();// List<User> users = sqlSession.selectList("test.selectalluser");UserMapper mapper = sqlSession.getMapper(UserMapper.class);List<User> users = mapper.selectAllUser();for (User user :users) {System.out.println(user.toString());}
}

结果:

在这里插入图片描述

这篇关于手写乞丐版-Mybatis的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/871187

相关文章

mybatis的整体架构

mybatis的整体架构分为三层: 1.基础支持层 该层包括:数据源模块、事务管理模块、缓存模块、Binding模块、反射模块、类型转换模块、日志模块、资源加载模块、解析器模块 2.核心处理层 该层包括:配置解析、参数映射、SQL解析、SQL执行、结果集映射、插件 3.接口层 该层包括:SqlSession 基础支持层 该层保护mybatis的基础模块,它们为核心处理层提供了良好的支撑。

Spring+MyBatis+jeasyui 功能树列表

java代码@EnablePaging@RequestMapping(value = "/queryFunctionList.html")@ResponseBodypublic Map<String, Object> queryFunctionList() {String parentId = "";List<FunctionDisplay> tables = query(parent

Mybatis中的like查询

<if test="templateName != null and templateName != ''">AND template_name LIKE CONCAT('%',#{templateName,jdbcType=VARCHAR},'%')</if>

JavaWeb【day09】--(Mybatis)

1. Mybatis基础操作 学习完mybatis入门后,我们继续学习mybatis基础操作。 1.1 需求 需求说明: 根据资料中提供的《tlias智能学习辅助系统》页面原型及需求,完成员工管理的需求开发。 通过分析以上的页面原型和需求,我们确定了功能列表: 查询 根据主键ID查询 条件查询 新增 更新 删除 根据主键ID删除 根据主键ID批量删除

MyBatis 切换不同的类型数据库方案

下属案例例当前结合SpringBoot 配置进行讲解。 背景: 实现一个工程里面在部署阶段支持切换不同类型数据库支持。 方案一 数据源配置 关键代码(是什么数据库,该怎么配就怎么配) spring:datasource:name: test# 使用druid数据源type: com.alibaba.druid.pool.DruidDataSource# @需要修改 数据库连接及驱动u

mybatis框架基础以及自定义插件开发

文章目录 框架概览框架预览MyBatis框架的核心组件MyBatis框架的工作原理MyBatis框架的配置MyBatis框架的最佳实践 自定义插件开发1. 添加依赖2. 创建插件类3. 配置插件4. 启动类中注册插件5. 测试插件 参考文献 框架概览 MyBatis是一个优秀的持久层框架,它支持自定义SQL、存储过程以及高级映射,为开发者提供了极大的灵活性和便利性。以下是关于M

mybatis if test 之 0当做参数传入出问题

首先前端传入了参数 if(StringUtils.isNotBlank(status)){requestParam.setProperty("status", Integer.parseInt(status));}List<SuperPojo> applicationList = groupDao.getApplicationListByReviewStatusAndMember(req

stl的sort和手写快排的运行效率哪个比较高?

STL的sort必然要比你自己写的快排要快,因为你自己手写一个这么复杂的sort,那就太闲了。STL的sort是尽量让复杂度维持在O(N log N)的,因此就有了各种的Hybrid sort algorithm。 题主你提到的先quicksort到一定深度之后就转为heapsort,这种是introsort。 每种STL实现使用的算法各有不同,GNU Standard C++ Lib

滚雪球学MyBatis(02):环境搭建

环境搭建 前言 欢迎回到我们的MyBatis系列教程。在上一期中,我们详细介绍了MyBatis的基本概念、特点以及它与其他ORM框架的对比。通过这些内容,大家应该对MyBatis有了初步的了解。今天,我们将从理论走向实践,开始搭建MyBatis的开发环境。了解并掌握环境搭建是使用MyBatis的第一步,也是至关重要的一步。 环境搭建步骤 在开始之前,我们需要准备一些必要的工具和软件,包括J

JS手写实现深拷贝

手写深拷贝 一、通过JSON.stringify二、函数库lodash三、递归实现深拷贝基础递归升级版递归---解决环引用爆栈问题最终版递归---解决其余类型拷贝结果 一、通过JSON.stringify JSON.parse(JSON.stringify(obj))是比较常用的深拷贝方法之一 原理:利用JSON.stringify 将JavaScript对象序列化成为JSO