Mini MyBatis-Plus(下)

作者简介:大家好,我是smart哥,前中兴通讯、美团架构师,现某互联网公司CTO

联系qq:184480602,加我进群,大家一起学习,一起进步,一起对抗互联网寒冬
 

最核心的内容前两篇已经讲完了,这一篇只有代码:

先看demo目录下的三个文件:

DemoApplication.java

package com.example.demo;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplication
public class DemoApplication {public static void main(String[] args) {SpringApplication.run(DemoApplication.class, args);}}

User.java

package com.example.demo;import com.example.demo.mybatisplus.annotations.TableName;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;import java.util.Date;/*** @author mx*/
@Data
@TableName("t_user")
@AllArgsConstructor
@NoArgsConstructor
public class User {private Long id;private String name;private Integer age;private Date birthday;
}

UserMapper.java

package com.example.demo;import com.example.demo.mybatisplus.AbstractBaseMapper;/*** @author mx*/
public class UserMapper extends AbstractBaseMapper<User> {
}

mybatisplus下AbstractBaseMapper.java

package com.example.demo.mybatisplus;import com.example.demo.mybatisplus.annotations.TableName;
import com.example.demo.mybatisplus.core.JdbcTemplate;
import com.example.demo.mybatisplus.query.QueryWrapper;
import com.example.demo.mybatisplus.query.SqlParam;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;/*** Mapper基类** @author mx*/
public abstract class AbstractBaseMapper<T> {private static Logger logger = LoggerFactory.getLogger(AbstractBaseMapper.class);private JdbcTemplate<T> jdbcTemplate = new JdbcTemplate<T>();private Class<T> beanClass;private final String TABLE_NAME;private static final String DEFAULT_LOGICAL_TYPE = " and ";public AbstractBaseMapper() {// DO对象的ClassbeanClass = (Class<T>) ((ParameterizedType) this.getClass().getGenericSuperclass()).getActualTypeArguments()[0];// DO对应的表名 TODO 非空判断及默认处理TABLE_NAME = beanClass.getAnnotation(TableName.class).value();}public T select(QueryWrapper<T> queryWrapper) {List<T> list = this.list(queryWrapper);if (!list.isEmpty()) {return list.get(0);}return null;}public List<T> list(QueryWrapper<T> queryWrapper) {StringBuilder sqlBuilder = new StringBuilder("SELECT * FROM ").append(TABLE_NAME).append(" WHERE ");List<Object> paramList = new ArrayList<>();Map<String, SqlParam> conditionMap = queryWrapper.build();conditionMap.forEach((operator, param) -> {sqlBuilder.append(param.getColumnName()).append(operator).append("?").append(DEFAULT_LOGICAL_TYPE);paramList.add(param.getValue());});// 删除最后一个 andString sql = sqlBuilder.replace(sqlBuilder.length() - DEFAULT_LOGICAL_TYPE.length(), sqlBuilder.length(), ";").toString();try {logger.info("sql: {}", sql);logger.info("params: {}", paramList);return jdbcTemplate.queryForList(sql, paramList, beanClass);} catch (Exception e) {e.printStackTrace();logger.error("query failed", e);}return Collections.emptyList();}public int insert(T bean) {// 得到DO对象的所有字段Field[] declaredFields = beanClass.getDeclaredFields();// 拼接sql语句,表名来自DO的TableName注解valueStringBuilder sqlBuilder = new StringBuilder().append("INSERT INTO ").append(TABLE_NAME).append(" VALUES(");for (int i = 0; i < declaredFields.length; i++) {sqlBuilder.append("?");if (i < declaredFields.length - 1) {sqlBuilder.append(",");}}sqlBuilder.append(")");// 收集sql参数ArrayList<Object> paramList = new ArrayList<>();try {for (Field declaredField : declaredFields) {declaredField.setAccessible(true);Object o = declaredField.get(bean);paramList.add(o);}} catch (IllegalAccessException e) {e.printStackTrace();}int affectedRows = 0;try {logger.info("sql: {}", sqlBuilder.toString());logger.info("params: {}", paramList);affectedRows = jdbcTemplate.update(sqlBuilder.toString(), paramList);logger.info("insert success, affectedRows: {}", affectedRows);return affectedRows;} catch (SQLException e) {e.printStackTrace();logger.error("insert failed", e);}return 0;}public int updateSelective(T bean, QueryWrapper<T> queryWrapper) {// 得到DO对象的所有字段Field[] declaredFields = beanClass.getDeclaredFields();// 拼接sql语句,表名来自DO的TableName注解valueStringBuilder sqlSetBuilder = new StringBuilder().append("UPDATE ").append(TABLE_NAME).append(" SET ");List<Object> paramList = new ArrayList<>();// 先拼接要SET的字段占位符 SET name=?, age=?try {for (int i = 0; i < declaredFields.length; i++) {Field declaredField = declaredFields[i];declaredField.setAccessible(true);Object fieldValue = declaredField.get(bean);if (fieldValue != null) {sqlSetBuilder.append(declaredField.getName()).append(" = ").append("?").append(", ");paramList.add(fieldValue);}}} catch (IllegalAccessException e) {e.printStackTrace();}// 删除最后一个 ,sqlSetBuilder = sqlSetBuilder.delete(sqlSetBuilder.length() - 2, sqlSetBuilder.length());// 再拼接WHERE条件占位符StringBuilder sqlWhereBuilder = new StringBuilder(" WHERE ");Map<String, SqlParam> conditionMap = queryWrapper.build();for (Map.Entry<String, SqlParam> stringSqlParamEntry : conditionMap.entrySet()) {String operator = stringSqlParamEntry.getKey();SqlParam param = stringSqlParamEntry.getValue();sqlWhereBuilder.append(param.getColumnName()).append(operator).append("?").append(DEFAULT_LOGICAL_TYPE);paramList.add(param.getValue());}// 删除最后一个 andsqlWhereBuilder = sqlWhereBuilder.replace(sqlWhereBuilder.length() - DEFAULT_LOGICAL_TYPE.length(), sqlWhereBuilder.length(), ";");String sql = sqlSetBuilder.append(sqlWhereBuilder).toString();int affectedRows = 0;try {logger.info("sql: {}", sqlSetBuilder.toString());logger.info("params: {}", paramList);affectedRows = jdbcTemplate.update(sql, paramList);logger.info("update success, affectedRows: {}", affectedRows);return affectedRows;} catch (SQLException e) {e.printStackTrace();logger.error("update failed", e);}return 0;}}

annotations下的TableName.java

package com.example.demo.mybatisplus.annotations;import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;/*** @author mx*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface TableName {String value();}

core下的

JdbcTemplate.java

package com.example.demo.mybatisplus.core;import java.lang.reflect.Field;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;/*** JdbcTemplate,简化jdbc操作** @author mx*/
public class JdbcTemplate<T> {public List<T> queryForList(String sql, List<Object> params, RowMapper<T> rowMapper) throws SQLException {return query(sql, params, rowMapper);}public T queryForObject(String sql, List<Object> params, RowMapper<T> rowMapper) throws SQLException {List<T> result = query(sql, params, rowMapper);return result.isEmpty() ? null : result.get(0);}public List<T> queryForList(String sql, List<Object> params, Class<T> clazz) throws Exception {return query(sql, params, clazz);}public T queryForObject(String sql, List<Object> params, Class<T> clazz) throws Exception {List<T> result = query(sql, params, clazz);return result.isEmpty() ? null : result.get(0);}public int update(String sql, List<Object> params) throws SQLException {// 1.获取ConnectionConnection conn = getConnection();// 2.传入sql模板、sql参数,得到PreparedStatementPreparedStatement ps = getPreparedStatement(sql, params, conn);// 3.执行更新(增删改)int affectedRows = ps.executeUpdate();// 4.释放资源closeConnection(conn, ps, null);return affectedRows;}// ************************* private methods **************************private List<T> query(String sql, List<Object> params, RowMapper<T> rowMapper) throws SQLException {// 外部传入rowMapper(手写规则)return baseQuery(sql, params, rowMapper);}private List<T> query(String sql, List<Object> params, Class<T> clazz) throws Exception {// 自己创建rowMapper(反射)后传入BeanHandler<T> beanHandler = new BeanHandler<>(clazz);return baseQuery(sql, params, beanHandler);}/*** 基础查询方法,必须传入Bean的映射规则** @param sql* @param params* @param rowMapper* @return* @throws SQLException*/private List<T> baseQuery(String sql, List<Object> params, RowMapper<T> rowMapper) throws SQLException {// TODO 参数非空校验// 1.获取ConnectionConnection conn = getConnection();// 2.传入sql模板、sql参数,得到PreparedStatementPreparedStatement ps = getPreparedStatement(sql, params, conn);// 3.执行查询ResultSet rs = ps.executeQuery();// 4.处理结果List<T> result = new ArrayList<>();while (rs.next()) {T obj = rowMapper.mapRow(rs);result.add(obj);}// 5.释放资源closeConnection(conn, ps, rs);return result;}/*** 内部类,实现了RowMapper接口,底层使用反射** @param <R>*/private static class BeanHandler<R> implements RowMapper<R> {// clazz表示最终封装的bean类型private Class<R> clazz;public BeanHandler(Class<R> clazz) {this.clazz = clazz;}@Overridepublic R mapRow(ResultSet rs) {try {if (rs.next()) {// 1.获取表数据ResultSetMetaData metaData = rs.getMetaData();// 2.反射创建beanR bean = clazz.newInstance();// 3.利用反射,把表数据设置到bean中for (int i = 0; i < metaData.getColumnCount(); i++) {String name = metaData.getColumnName(i + 1);Object value = rs.getObject(name);Field field = clazz.getDeclaredField(name);field.setAccessible(true);field.set(bean, value);}// 4.返回beanreturn bean;} else {return null;}} catch (Exception e) {throw new RuntimeException(e);}}}private PreparedStatement getPreparedStatement(String sql, List<Object> params, Connection conn) throws SQLException {// 1.传入sql模板,得到PreparedStatementPreparedStatement ps = conn.prepareStatement(sql);// 2.为sql模板设置参数for (int i = 0; i < params.size(); i++) {ps.setObject(i + 1, params.get(i));}return ps;}private Connection getConnection() throws SQLException {// TODO 可以抽取配置到properties文件String url = "jdbc:mysql://localhost:3306/demo";String user = "root";String password = "123456";return DriverManager.getConnection(url, user, password);}private void closeConnection(Connection conn, PreparedStatement preparedStatement, ResultSet rs) throws SQLException {if (rs != null) {rs.close();}if (preparedStatement != null) {preparedStatement.close();}if (conn != null) {conn.close();}}}

 RowMapper.java

package com.example.demo.mybatisplus.core;import java.sql.ResultSet;/*** 结果集映射器** @author mx*/
@FunctionalInterface
public interface RowMapper<T> {/*** 将结果集转为指定的Bean** @param resultSet* @return*/T mapRow(ResultSet resultSet);
}

query下的

QueryWrapper.java

package com.example.demo.mybatisplus.query;import com.example.demo.mybatisplus.utils.ConditionFunction;
import com.example.demo.mybatisplus.utils.Reflections;import java.util.HashMap;
import java.util.Map;/*** 模拟MyBatis-Plus的LambdaQueryWrapper(思路完全不同,仅仅是形似)** @author mx*/
public class QueryWrapper<T> {// conditionMap,收集查询条件// {//    " LIKE ": {//        "name": "bravo1988"//    },//    " = ": {//        "age": 18//    }// }private final Map<String, SqlParam> conditionMap = new HashMap<>();// 操作符类型,比如 name like 'bravo' 中的 LIKEprivate static final String OPERATOR_EQ = " = ";private static final String OPERATOR_GT = " > ";private static final String OPERATOR_LT = " < ";private static final String OPERATOR_LIKE = " LIKE ";public QueryWrapper<T> eq(ConditionFunction<T, ?> fn, Object value) {String columnName = Reflections.fnToColumnName(fn);conditionMap.put(OPERATOR_EQ, new SqlParam(columnName, value));return this;}public QueryWrapper<T> gt(ConditionFunction<T, ?> fn, Object value) {String columnName = Reflections.fnToColumnName(fn);conditionMap.put(OPERATOR_GT, new SqlParam(columnName, value));return this;}public QueryWrapper<T> lt(ConditionFunction<T, ?> fn, Object value) {String columnName = Reflections.fnToColumnName(fn);conditionMap.put(OPERATOR_LT, new SqlParam(columnName, value));return this;}public QueryWrapper<T> like(ConditionFunction<T, ?> fn, Object value) {String columnName = Reflections.fnToColumnName(fn);conditionMap.put(OPERATOR_LIKE, new SqlParam(columnName, "%" + value + "%"));return this;}public Map<String, SqlParam> build() {return conditionMap;}
}

 SqlParam.java

package com.example.demo.mybatisplus.query;import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;/*** @author mx*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class SqlParam {private String columnName;private Object value;
}

utils下的

 ConditionFunction.java

package com.example.demo.mybatisplus.utils;import java.io.Serializable;
import java.util.function.Function;/*** 扩展java.util.function包下的Function接口:支持Serializable* 搭配Reflections工具类一起使用,用于获取Lambda表达式的方法名** @author mx*/
@FunctionalInterface
public interface ConditionFunction<T, R> extends Function<T, R>, Serializable {
}

Reflections.java

package com.example.demo.mybatisplus.utils;import java.beans.Introspector;
import java.lang.invoke.SerializedLambda;
import java.lang.reflect.Method;
import java.util.regex.Pattern;/*** 获取Lambda入参的方法名** @author mx*/
public class Reflections {private static final Pattern GET_PATTERN = Pattern.compile("^get[A-Z].*");private static final Pattern IS_PATTERN = Pattern.compile("^is[A-Z].*");/*** 注意: 非标准变量(非小驼峰)调用这个方法可能会有问题** @param fn* @param <T>* @return*/public static <T> String fnToColumnName(ConditionFunction<T, ?> fn) {try {Method method = fn.getClass().getDeclaredMethod("writeReplace");method.setAccessible(Boolean.TRUE);SerializedLambda serializedLambda = (SerializedLambda) method.invoke(fn);String getter = serializedLambda.getImplMethodName();// 对于非标准变量生成的Get方法这里可以直接抛出异常,或者打印异常日志if (GET_PATTERN.matcher(getter).matches()) {getter = getter.substring(3);} else if (IS_PATTERN.matcher(getter).matches()) {getter = getter.substring(2);}return Introspector.decapitalize(getter);} catch (ReflectiveOperationException e) {throw new RuntimeException(e);}}
}

其实第一篇的内容是最难的,不只是从0到1,而是从0到90,后面两篇其实只是90到100,在这基础稍微扩展了一下而已。

AbstractBaseMapper代码还有冗余,有兴趣的同学可以自行完善。但还是那句话,如果你的目的是为了锻炼封装能力,可以精益求精,但我们的AbstractBaseMapper注定不能用于生产,即使要优化,点到为止即可。

学习必须往深处挖,挖的越深,基础越扎实!

阶段1、深入多线程

阶段2、深入多线程设计模式

阶段3、深入juc源码解析

阶段4、深入jdk其余源码解析

阶段5、深入jvm源码解析

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

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

相关文章

docker 安装可视化工具 Protainer 以及 汉化

一、创建保存数据的卷 安装网址&#xff1a;Install Portainer BE with Docker on Linux - Portainer Documentation docker pull portainer/portainer二、根据portainer镜像创建容器 docker run -d -p 8000:8000 -p 9000:9000\ --name portainer --restartalways \ -v /var/r…

深入ArkUI:深入实战组件text和text input

文章目录 Text组件介绍Text组件的属性方法Text:文本显示组件4.3TextInput组件实战案例:图片宽度控制页面本文总结要点回顾在今天的课程中,我们将深入学习ArkUI提供的基础组件,着重探讨text和text input两个组件。 Text组件介绍 Text组件是一个用于显示文本的组件,其主要作…

VSCode安装Go环境

VSCode安装Go 1.点击Go官网&#xff0c;根据自己环境下载go安装包&#xff0c;我这里为Windows 2.双击安装包&#xff0c;一直点击【Next】即可 VSCode配置Go基础环境 1.创建Go的工作目录: C:\Code\GoCode 2.创建Go的环境变量: GOPATH (1)右键【此电脑】&#xff0c;点击…

C/C++转WebAssembly及微信小程序调用

上一篇文章讲了C/C如何转WebAssembly&#xff0c;并测试了在Web端调用。本篇内容和上篇一样&#xff0c;介绍C/C包转的.wasm包如何在小程序中调用。 说明 本篇是在上一篇步骤1-4的基础上&#xff0c;再做修改&#xff0c;供微信小程序端调用的方法和步骤。 本篇操作手册可以…

Python自动化测试:选择最佳的自动化测试框架

在开始学习python自动化测试之前&#xff0c;先了解目前市场上的自动化测试框架有哪些&#xff1f; 随着技术的不断迭代更新&#xff0c;优胜劣汰也同样发展下来。从一开始工具型自动化&#xff0c;到现在的框架型&#xff1b;从一开始的能用&#xff0c;到现在的不仅能用&…

鸿蒙问题之本地模拟器无法识别

今天按例打开本地模拟器&#xff0c;发现DevEco Studio不能检测到我的本地模拟器了。 重启了DevEco Studio和模拟器多次都无果。果断删除模拟器 然后创建一个新的&#xff0c;就可以成功检测到了。这应该是idea的一个bug

关于python解析mf4中二维信号数据的注意事项

python解析mf4中的信号数据一般用np.ndarray存储&#xff0c;但是mf4中的一个信号有时不一定是一维数据&#xff0c;有时会是一个二维的&#xff0c;没错&#xff0c;就是一个信号数据就是二维的&#xff0c;这时候&#xff0c;np数组的每个元素也是一个数组&#xff0c;这个时…

整数除法相关用法:floor、ceil、round

整数除法——舍小数&#xff0c;取整数 1.floor()头文件<math.h> 功能&#xff1a;把一个小数向下取整&#xff0c;即就是如果数是2.2 &#xff0c;那向下取整的结果就为2.000000 原型&#xff1a;double floor(doube x); x : 是需要计算的数 返回值&#xff1a; 成…

【揭秘】如何制作推拉门电子画册,轻松成为行业大咖!

​在当今数字化时代&#xff0c;电子画册已成为企业展示产品、服务的重要手段。与传统印刷画册相比&#xff0c;电子画册具有诸多优势&#xff0c;如易于传播、易于更新、环保低碳等。更重要的是&#xff0c;通过电子画册&#xff0c;企业可以更好地与目标受众互动&#xff0c;…

K8s - Helm、HPA、rancher

目录 1、什么是 Helm 2、Helm 部署 3、Helm 自定义模板 4、Helm 仓库 5、HPA 6、部署 metrics-server 7、Rancher 管理 Kubernetes 集群 8、Rancher 安装及配置 1、什么是 Helm 在没使用 helm 之前&#xff0c;向 kubernetes 部署应用&#xff0c;我们要依次部署 depl…

天津医科大学临床医学院专升本药学专业有机化学考试大纲

天津医科大学临床医学院高职升本科专业课考试大纲药学专业《有机化学》科目考试大纲 一、考试基本要求 本考试大纲主要要求考生对《有机化学》基本概念有较深入的了解&#xff0c;能够系统地掌握各类化合物的命名、结构特点及立体异构、主要性质、反应、来源和合成制备方法等…

SQL面试题挑战11:访问会话切割

目录 问题&#xff1a;SQL解答&#xff1a; 问题&#xff1a; 如下为某电商公司用户访问网站的数据&#xff0c;包括用户id和访问时间两个字段。现有如下规则&#xff1a;如果某个用户的连续的访问记录时间间隔小于60秒&#xff0c;则属于同一个会话&#xff0c;现在需要计算每…

相对于一般的统计学,计量经济学的特色是什么?谈Stata与计量经济学

Stata作为一种数据分析软件&#xff0c;高度适用于依托计量经济学的研究领域&#xff0c;如宏观经济学、财政学等&#xff0c;当然在医学等学科应用也较为广泛&#xff0c;在处理面板数据方面也深具特色。计量经济学是指运用概率统计方法对经济变量之间的因果关系进行定量分析的…

idea的pom.xml文件灰色删除线解决办法

以上是点击了移除module后就变成这样 如果再次对着已移除的module右键会发现有个delete&#xff0c;点击这个是真删了&#xff0c;要谨慎备份哦 解决方案&#xff1a;恢复误操作remove module的解决方法 idea最右边&#xff0c;有个Maven控件&#xff0c;找到要恢复的module&a…

vscode连接linux服务器

目录 下载vscode&#xff0c;这是微软开源软件&#xff0c;打开后到下载扩展页面 在下载扩展页面下载中文和ssh远程连接扩展 安装后会在左边新生成一个图标点击齿轮 选择第一个 配置连接信息 远程隧道右边刷新&#xff0c;等刷出来hostname的主机后 连接ip出来后&#x…

mybatisX自动生成sql语句,尝试测试方法报错

今天我使用mybatisx自定义mapper方法生成sql语句后&#xff0c;在测试时报错 错误是MyBatis 无法找到映射的语句&#xff08;Statement&#xff09;引起的 我是这样操作的&#xff0c;在mapper接口自定义了一个方法 然后alt加enter&#xff0c;自动生成sql 结果 mapper.xml文件…

骨传导耳机的原理是什么?一文读懂骨传导耳机优缺点都有哪些!

一、骨传导耳机传声原理是什么 骨传导耳机以人体骨骼为传声介质&#xff0c;可以将声音转化为不同频率的震动&#xff0c;在不经过外耳道和鼓膜的情况下&#xff0c;通过震动使声音经过内耳道&#xff0c;直接传入大脑听觉神经&#xff0c;与传统耳机相比&#xff0c;可以节省许…

Gooxi成功入选全国首批人工智能企业

日前&#xff0c;Gooxi成功入选由深圳市人工智能产业协会评定的全国首批36家人工智能企业之一&#xff0c;这是继Gooxi成功与广东未来研究院合作AI联合创新中心之后在AI行业持续深耕的又一殊荣&#xff0c;彰显出Gooxi在AI领域的卓越实力。 据悉&#xff0c;全国首批人工智能企…

第 1 场 算法季度赛 蓝桥搜狐畅游(1~5 , 7)

1、水题 2、树上dp 3、模拟 4、概率 5、拆位 6、&#xff08;是没学过的东西了...&#xff09; 7、组合数学 1. 新年快乐【算法赛】 直接模拟 #include <iostream> using namespace std; int main() {cout <<"2024 AK";return 0; } 2. 蓝桥圣诞树…

【每日一题】收集巧克力

文章目录 Tag题目来源题目解读解题思路方法一&#xff1a;枚举操作数 写在最后 Tag 【枚举】【数组】【2023-12-28】 题目来源 2735. 收集巧克力 题目解读 有长度为 n, 下标从 0 开始的整数数组 nums, 表示收集不同类型的巧克力的成本. nums[i] 表示收集类型 i 巧克力的成本…