纯手写Myatis框架
1、接口层-和数据库交互的方式
MyBatis和数据库的交互有两种方式:
使用传统的MyBatis提供的API;
使用Mapper接口;
2、使用Mapper接口
MyBatis 将配置文件中的每一个<mapper> 节点抽象为一个 Mapper 接口:
这个接口中声明的方法和<mapper> 节点中的<select|update|delete|insert> 节点项对应,即<select|update|delete|insert> 节点的id值为Mapper 接口中的方法名称,
parameterType 值表示Mapper 对应方法的入参类型,而resultMap 值则对应了Mapper 接口表示的返回值类型或者返回结果集的元素类型。
根据MyBatis 的配置规范配置好后,通过SqlSession.getMapper(XXXMapper.class)方法,MyBatis 会根据相应的接口声明的方法信息,通过动态代理机制生成一个Mapper 实例,
我们使用Mapper接口的某一个方法时,MyBatis会根据这个方法的方法名和参数类型,确定Statement Id,底层还是通过SqlSession.select("statementId",parameterObject);或者SqlSession.update("statementId",parameterObject); 等等来实现对数据库的操作,MyBatis引用Mapper 接口这种调用方式,纯粹是为了满足面向接口编程的需要。
(其实还有一个原因是在于,面向接口的编程,使得用户在接口上可以使用注解来配置SQL语句,这样就可以脱离XML配置文件,实现“0配置”)。
3、数据处理层
数据处理层可以说是MyBatis的核心,从大的方面上讲,它要完成两个功能:
通过传入参数构建动态SQL语句;
SQL语句的执行以及封装查询结果集成List<E>;
4、主要构件及其相互关系
从MyBatis代码实现的角度来看,MyBatis的主要的核心部件有以下几个:
SqlSession:作为MyBatis工作的主要顶层API,表示和数据库交互的会话,完成必要数据库增删改查功能;
Executor:MyBatis执行器,是MyBatis 调度的核心,负责SQL语句的生成和查询缓存的维护;
StatementHandler:封装了JDBC Statement操作,负责对JDBC statement 的操作,如设置参数、将Statement结果集转换成List集合。
ParameterHandler:负责对用户传递的参数转换成JDBC Statement 所需要的参数;
ResultSetHandler:负责将JDBC返回的ResultSet结果集对象转换成List类型的集合;
TypeHandler:负责java数据类型和jdbc数据类型之间的映射和转换;
MappedStatement:MappedStatement维护了一条<select|update|delete|insert>节点的封装;
SqlSource:负责根据用户传递的parameterObject,动态地生成SQL语句,将信息封装到BoundSql对象中,并返回;
BoundSql:表示动态生成的SQL语句以及相应的参数信息;
Configuration:MyBatis所有的配置信息都维持在Configuration对象之中;
5、纯手写Mybatis代码
思想:自定义注解+反射+动态代理实现
添加依赖包
<dependencies>
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.37</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.8</version>
</dependency>
</dependencies>
定义JDBCUtils:
package com.yehui.utils; import sun.security.krb5.Config; import java.sql.*;
import java.util.List; public class JDBCUtils {
private static String connect;
private static String driverClassName;
private static String URL;
private static String username;
private static String password;
private static boolean autoCommit; private static Connection conn;
private static PreparedStatement pre;
static {
Config();
}
private static void Config(){
/*
* 获取驱动
*/
driverClassName = "com.mysql.jdbc.Driver";
/*
* 获取URL
*/
URL = "jdbc:mysql://localhost:3306/study?useUnicode=true&characterEncoding=utf8";
/*
* 获取用户名
*/
username = "root";
/*
* 获取密码
*/
password = "root";
/*
* 设置是否自动提交,一般为false不用改
*/
autoCommit = false;
}
/**
* 载入数据库驱动类
*/
private static boolean load(){
try {
Class.forName(driverClassName);
return true;
} catch (ClassNotFoundException e) {
System.out.println("驱动类 " + driverClassName + " 加载失败");
e.printStackTrace();
}
return false;
}
/**
* 专门检查缓存的连接是否不可以被使用 ,不可以被使用的话,就返回 true
*/
private static boolean invalid() {
if (conn != null) {
try {
if (conn.isClosed() || !conn.isValid(3)) {
return true;
/*
* isValid方法是判断Connection是否有效,如果连接尚未关闭并且仍然有效,则返回 true
*/
}
} catch (SQLException e) {
e.printStackTrace();
}
/*
* conn 既不是 null 且也没有关闭 ,且 isValid 返回 true,说明是可以使用的 ( 返回 false )
*/
return false;
} else {
return true;
}
}
/**
* 建立数据库连接
*/
public static Connection connect() {
if (invalid()) { /* invalid为true时,说明连接是失败的 */
/* 加载驱动 */
load();
try {
/* 建立连接 */
conn = DriverManager.getConnection(URL, username, password);
} catch (SQLException e) {
System.out.println("建立 " + connect + " 数据库连接失败 , " + e.getMessage());
}
}
return conn;
}
/**
* 设置是否自动提交事务
**/
public static void transaction() { try {
conn.setAutoCommit(autoCommit);
} catch (SQLException e) {
System.out.println("设置事务的提交方式为 : " + (autoCommit ? "自动提交" : "手动提交") + " 时失败: " + e.getMessage());
}
} /** 提交事务 */
private static void commit(Connection c) {
if (c != null && !autoCommit) {
try {
c.commit();
} catch (SQLException e) {
e.printStackTrace();
}
}
} /** 回滚事务 */
private static void rollback(Connection c) {
if (c != null && !autoCommit) {
try {
c.rollback();
} catch (SQLException e) {
e.printStackTrace();
}
}
} /**
* 释放资源
**/
public static void release(Object cloaseable) { if (cloaseable != null) { if (cloaseable instanceof ResultSet) {
ResultSet rs = (ResultSet) cloaseable;
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
} if (cloaseable instanceof Statement) {
Statement st = (Statement) cloaseable;
try {
st.close();
} catch (SQLException e) {
e.printStackTrace();
}
} if (cloaseable instanceof Connection) {
Connection c = (Connection) cloaseable;
try {
c.close();
} catch (SQLException e) {
e.printStackTrace();
}
} } }
/**
* 根据给定的带参数占位符的SQL语句,创建 PreparedStatement 对象
*
* @param SQL
* 带参数占位符的SQL语句
* @return 返回相应的 PreparedStatement 对象
*/
private static PreparedStatement prepare(String SQL, boolean autoGeneratedKeys) { PreparedStatement ps = null;
connect();
/* 如果连接是无效的就重新连接 */
transaction();
/* 设置事务的提交方式 */
try {
if (autoGeneratedKeys) {
ps = conn.prepareStatement(SQL, Statement.RETURN_GENERATED_KEYS);
} else {
ps = conn.prepareStatement(SQL);
}
} catch (SQLException e) {
System.out.println("创建 PreparedStatement 对象失败: " + e.getMessage());
} return ps; }
/*
*
* @param SQL 需要执行的 INSERT 语句
*
* @param autoGeneratedKeys 指示是否需要返回由数据库产生的键
*
* @param params 将要执行的SQL语句中包含的参数占位符的 参数值
*
* @return 如果指定 autoGeneratedKeys 为 true 则返回由数据库产生的键; 如果指定 autoGeneratedKeys
* 为 false 则返回受当前SQL影响的记录数目
*/
public static Object insert(String SQL, List<?> params,boolean autoGeneratedKeys) {
//获得连接
conn = connect();
int var = 0;
try {
if (SQL == null || SQL.trim().isEmpty()) {
throw new RuntimeException("你没有指定SQL语句,请检查是否指定了需要执行的SQL语句");
}
// 如果不是 insert 开头开头的语句
if (!SQL.trim().toLowerCase().startsWith("insert")) {
System.out.println(SQL.toLowerCase());
throw new RuntimeException("你指定的SQL语句不是插入语句,请检查你的SQL语句");
}
if (params!=null&¶ms.size()>0) {//有参数的情况
//创建连接对象
pre = prepare(SQL,autoGeneratedKeys);
for (int i = 0; i <params.size(); i++) {
pre.setObject(i+1,typeof(params.get(i)));
}
int count = pre.executeUpdate();
if (autoGeneratedKeys) {// 如果希望获得数据库产生的键
ResultSet rs = pre.getGeneratedKeys(); // 获得数据库产生的键集
if (rs.next()) { // 因为是保存的是单条记录,因此至多返回一个键
var = rs.getInt(1); // 获得值并赋值给 var 变量
}
}else{
var = count;// 如果不需要获得,则将受SQL影像的记录数赋值给 var 变量
}
commit(conn);
}else{// 说明没有参数
Statement st = statement();
Connection connection = st.getConnection();
int count = st.executeUpdate(SQL);
if (autoGeneratedKeys) { // 如果企望获得数据库产生的键
ResultSet rs = st.getGeneratedKeys(); // 获得数据库产生的键集
if (rs.next()) { // 因为是保存的是单条记录,因此至多返回一个键
var = rs.getInt(1); // 获得值并赋值给 var 变量
}
} else {
var = count; // 如果不需要获得,则将受SQL影像的记录数赋值给 var 变量
}
commit(conn); // 提交事务
}
} catch (Exception e) {
System.out.println("数据保存失败: " + e.getMessage());
rollback(conn);
}
return var;
} /**
* 创建 Statement 对象
*/
public static Statement statement() {
Statement st = null;
connect();
/* 如果连接是无效的就重新连接 */
transaction();
/* 设置事务的提交方式 */
try {
st = conn.createStatement();
} catch (SQLException e) {
System.out.println("创建 Statement 对象失败: " + e.getMessage());
} return st;
} /**
* 类型转换
* @param o
* @return
*/
private static Object typeof(Object o) {
Object r = o; if (o instanceof java.sql.Timestamp) {
return r;
}
// 将 java.util.Date 转成 java.sql.Date
if (o instanceof java.util.Date) {
java.util.Date d = (java.util.Date) o;
r = new java.sql.Date(d.getTime());
return r;
}
// 将 Character 或 char 变成 String
if (o instanceof Character || o.getClass() == char.class) {
r = String.valueOf(o);
return r;
}
return r;
} /**
* 得到查询结
* @param newSql
* @param parameValues
* @return
*/
public static ResultSet select(String newSql, List<Object> parameValues) {
ResultSet rs = null;
//获得连接
conn = connect();
if (newSql == null || newSql.trim().isEmpty() || !newSql.trim().toLowerCase().startsWith("select")) {
throw new RuntimeException("你的SQL语句为空或不是查询语句");
}
try {
if (parameValues!=null&¶meValues.size()>0) {//这是有参数的情况下
//获取连接对象
pre = conn.prepareStatement(newSql);
for (int i = 0; i < parameValues.size(); i++) {
pre.setObject(i+1,parameValues.get(i));
}
rs = pre.executeQuery();
}else{
/* 说明没有传入任何参数 */
Statement st = statement();
rs = st.executeQuery(newSql); // 直接执行不带参数的 SQL 语句
}
} catch (Exception e) {
System.out.println("执行SQL失败: " + e.getMessage());
}
return rs;
}
}
定义SQLUtils
package com.yehui.utils; import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List; public class SQLUtils { /**
* 得到插入sql需要传递的参数
* @param insertSql
*/
public static List<String> getInsertSqlParams(String insertSql){
Integer startIndex = insertSql.indexOf("values");
insertSql = insertSql.substring(startIndex+"values".length())
.replace("#{","")
.replace("}","")
.replace("(","")
.replace(")","");
return Arrays.asList(insertSql.split(","));
}
/**
* 得到查询sql需要传递的参数
* @param selectSql
*/
public static List<String> getSelectSqlParams(String selectSql){
Integer startIndex = selectSql.indexOf("where");
selectSql = selectSql.substring(startIndex+"where".length());
String[] ands = selectSql.split("and");
List<String> list = new ArrayList<>();
for (String and : ands) {
String[] split = and.split("=");
String replace = split[1].replace("#{", "").replace("}", "").trim();
list.add(replace);
}
return list;
}
/**
* 将sql参数转换为?
* @param sql
* @param params
* @return
*/
public static String covertSql(String sql,List<String> params){
for (String param : params) {
sql = sql.replace("#{"+param+"}","?");
}
return sql;
} public static void main(String[] args) {
//insert into account(username,money) values(#{username},#{money})
//select * from account where username=#{username} and money=#{money}
List<String> p = new ArrayList<>();
p.add("username");
p.add("money");
getSelectSqlParams("select * from account where username=#{username} and money=#{money}");
}
}
定义一个mapper接口
package com.yehui.mapper; import com.yehui.annotation.ExtInsert;
import com.yehui.annotation.ExtParam;
import com.yehui.annotation.ExtSelect;
import com.yehui.entity.User; public interface UserMapper {
// 1.接口既然不能被实例化?那么我们是怎么实现能够调用的?
// 2.参数如何和sql参数绑定
// 3.返回结果
@ExtSelect("select * from account where username=#{username} and money=#{money} ")
User selectUser(@ExtParam("username") String username, @ExtParam("money") Integer money); @ExtInsert("insert into account(username,money) values(#{username},#{money})")
int insertUser(@ExtParam("username") String username, @ExtParam("money") Integer money);
}
定义注解
@Target({ ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
public @interface ExtInsert { String value();
} // Mapper映射注解
@Target({ ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface ExtMapper { } @Target(value = { ElementType.PARAMETER })
@Retention(RetentionPolicy.RUNTIME)
public @interface ExtParam { String value(); } // 查询注解
@Target({ ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
public @interface ExtSelect { String value(); }
定义动态代理类
package com.yehui; import com.sun.xml.internal.ws.api.model.MEP;
import com.yehui.annotation.ExtInsert;
import com.yehui.annotation.ExtParam;
import com.yehui.annotation.ExtSelect;
import com.yehui.utils.JDBCUtils;
import com.yehui.utils.SQLUtils; import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap; public class MyInvocationHandlerMbatis implements InvocationHandler {
/**
* 这个就是我们要代理的真实对象
*/
private Object target; /**
* 构造方法,给我们要代理的真实对象赋初值
*
* @param target
*/
public MyInvocationHandlerMbatis(Object target) {
this.target = target;
}
/**
* 该方法负责集中处理动态代理类上的所有方法调用。 调用处理器根据这三个参数进行预处理或分派到委托类实例上反射执行
*
* @param proxy
* 代理类实例
* @param method
* 被调用的方法对象
* @param args
* 调用参数
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//获取注解
if(method.isAnnotationPresent(ExtInsert.class)){
ExtInsert extInsert = method.getAnnotation(ExtInsert.class);
//得到注解上面的sql
String insertSql = extInsert.value();
//得到参数列表
ConcurrentHashMap<String,Object> paramMap = getExtParams(method,args);
//得到注解上面的参数
List<String> params = SQLUtils.getInsertSqlParams(insertSql);
//转换sql 替换为?
String newSql = SQLUtils.covertSql(insertSql, params);
//得到参数值
List<Object> parameValues = new ArrayList<>();
for (String parame : params) {
parameValues.add(paramMap.get(parame));
}
//执行sql语句
return JDBCUtils.insert(newSql,parameValues,false);
}
//得到查询注解
if(method.isAnnotationPresent(ExtSelect.class)){
//得到sql上面的注解
ExtSelect extSelect = method.getAnnotation(ExtSelect.class);
//得到注解上的值
String selectSql = extSelect.value();
//得到参数列表
ConcurrentHashMap<String,Object> paramMap = getExtParams(method,args);
//得到注解上面的参数
List<String> params = SQLUtils.getSelectSqlParams(selectSql);
//转换sql 替换为?
String newSql = SQLUtils.covertSql(selectSql, params);
//得到参数值
List<Object> parameValues = new ArrayList<>();
for (String parame : params) {
parameValues.add(paramMap.get(parame));
}
//执行sql得到查询结果
ResultSet rs = JDBCUtils.select(newSql,parameValues);
if (!rs.next()) {
// 没有查找数据
return null;
}
//得到返回值类型
Class<?> returnType = method.getReturnType();
// 向上移动
rs.previous();
//得到实例化对象
Object obj = returnType.newInstance();
while (rs.next()){
for(String parm:params){
//得到对应的属性
Field declaredField = returnType.getDeclaredField(parm);
declaredField.setAccessible(true);
// 赋值参数
declaredField.set(obj,rs.getObject(parm));
}
}
return obj;
}
return null;
} /**
* 将参数放入map中
*/
public static ConcurrentHashMap<String,Object> getExtParams(Method method,Object[] args){
Parameter[] parameters = method.getParameters();
ConcurrentHashMap<String,Object> paramMap = new ConcurrentHashMap<>();
for (int i = 0; i < parameters.length; i++) {
// 参数信息
Parameter parameter = parameters[i];
if(parameter.isAnnotationPresent(ExtParam.class)){
ExtParam extParam = parameter.getAnnotation(ExtParam.class);
// 参数名称
String value = extParam.value();
//args[i] 参数值
paramMap.put(value,args[i]);
}
}
return paramMap;
}
}
创建SqlSession
public class SqlSession { public static <T> T getMapper(Class<T> tClass){
return (T) Proxy.newProxyInstance(tClass.getClassLoader(),new Class[]{tClass},
new MyInvocationHandlerMbatis(tClass));
}
}
创建一个实体
package com.yehui.entity; public class User {
private String username;
private Integer money;
private Integer id; public String getUsername() {
return username;
} public void setUsername(String username) {
this.username = username;
} public Integer getMoney() {
return money;
} public void setMoney(Integer money) {
this.money = money;
} public Integer getId() {
return id;
} public void setId(Integer id) {
this.id = id;
} @Override
public String toString() {
return "User{" + "username='" + username + '\'' + ", money=" + money + ", id=" + id + '}';
}
}
测试查询:
UserMapper mapper = SqlSession.getMapper(UserMapper.class);
User jack = mapper.selectUser("jack", 6000);
System.out.println(jack);
测试新增:
UserMapper mapper = SqlSession.getMapper(UserMapper.class);
int count = mapper.insertUser("user111", 1000);
System.out.println(count);
网站:https://blog.csdn.net/kuailebuzhidao/article/details/88355236
纯手写Myatis框架的更多相关文章
- 纯手写SpringMVC框架,用注解实现springmvc过程
闲话不多说,直接上代码! 1.第一步,首先搭建如下架构,其中,annotation中放置自己编写的注解,主要包括service controller qualifier RequestMapping ...
- vue10行代码实现上拉翻页加载更多数据,纯手写js实现下拉刷新上拉翻页不引用任何第三方插件
vue10行代码实现上拉翻页加载更多数据,纯手写js实现下拉刷新上拉翻页不引用任何第三方插件/库 一提到移动端的下拉刷新上拉翻页,你可能就会想到iScroll插件,没错iScroll是一个高性能,资源 ...
- 纯手撸web框架
纯手撸web框架 一.Web应用的组成 接下来我们学习的目的是为了开发一个Web应用程序,而Web应用程序是基于B/S架构的,其中B指的是浏览器,负责向S端发送请求信息,而S端会根据接收到的请求信息返 ...
- springmvc 动态代理 JDK实现与模拟JDK纯手写实现。
首先明白 动态代理和静态代理的区别: 静态代理:①持有被代理类的引用 ② 代理类一开始就被加载到内存中了(非常重要) 动态代理:JDK中的动态代理中的代理类是动态生成的.并且生成的动态代理类为$Pr ...
- 简易-五星评分-jQuery纯手写
超级简单的评分功能,分为四个步骤轻松搞定: 第一步: 引入jquery文件:这里我用百度CDN的jquery: <script src="http://apps.bdimg.com/l ...
- 超级简单的jQuery纯手写五星评分效果
超级简单的评分功能,分为四个步骤轻松搞定: 第一步: 引入jquery文件:这里我用百度CDN的jquery: <script src="http://apps.bdimg.com/l ...
- 手写 jQuery 框架
1.测试页面; <!DOCTYPE html> <html lang="en"> <head> <meta charset="U ...
- 手写DAO框架(三)-数据库连接
-------前篇:手写DAO框架(二)-开发前的最后准备--------- 前言 上一篇主要是温习了一下基础知识,然后将整个项目按照模块进行了划分.因为是个人项目,一个人开发,本人采用了自底向上的开 ...
- 手写DAO框架(二)-开发前的最后准备
-------前篇:手写DAO框架(一)-从“1”开始 --------- 前言:前篇主要介绍了写此框架的动机,把主要功能点大致介绍了一下.此篇文章主要介绍开发前最后的一些准备.主要包括一些基础知识点 ...
随机推荐
- Git add命令
git add -A和 git add . git add -u在功能上看似很相近,但还是存在一点差别 git add . :他会监控工作区的状态树,使用它会把工作时的所有变化提交到暂存区,包括文 ...
- Asp.net Mvc Action重定向总结
摘自博客园 程晓晖 [HttpPost] public ActionResult StudentList( string StudName, string studName, DateT ...
- RDD算子、RDD依赖关系
RDD:弹性分布式数据集, 是分布式内存的一个抽象概念 RDD:1.一个分区的集合, 2.是计算每个分区的函数 , 3.RDD之间有依赖关系 4.一个对于key-value的RDD的Partit ...
- Js中的假值_ES5中定义的ToBoolean方法强制类型转换后值为false
你不知道的Javascript(中)--ToBoolean javascript中的值可以分为以下两类: 1.可以被强制类型转换为false的值 2.其他(被强制类型转换为true的值) 假值---以 ...
- 做一个日收入100元的APP!
[导语]虽然讲了很多个人开发者的文章,但新手开发者如何赚自己的第一个100块钱,确是最难的事情.群里有人说都不知道干什么 app赚钱,完全没有想法, 并且经常问我有什么快速赚钱的方法.我只能遗憾地说, ...
- 设计模式之第9章-原型模式(Java实现)
设计模式之第9章-原型模式(Java实现) “快到春节了,终于快放假了,天天上班好累的说.”“确实啊,最近加班比较严重,项目快到交付了啊.”“话说一到过节,就收到铺天盖地的短信轰炸,你说发短信就发吧, ...
- 1、HTML基础总结 part-1
1.基本标签属性 <html> <!--属性和属性值对大小写不敏感. 不过,万维网联盟在其 HTML 4 推荐标准中推荐小写的属性/属性值. 而新版本的 (X)HTML 要求使用小写 ...
- 39、apk瘦身(转载)
本文转自::Android开发中文站 » 关于APK瘦身值得分享的一些经验 从APK的文件结构说起 APK在安装和更新之前都需要经过网络将其下载到手机,如果APK越大消耗的流量就会越多,特别是对于使用 ...
- ogre3D学习基础13 -- 键盘控制网格动画mesh
以上一节为蓝本,这里增加一点难度,添加了四个节点,增加键盘控制移动速度,使用bool变量控制是否移动. 第一,要增加键盘控制,那就使用OIS::KeyListener,在监听器里添加一个父类KeyLi ...
- 聊聊、Integer 封装特性
前几天在公司内部群,有人分享出了一道题,问谁能口算出来,他就膜拜那个人.题目如下: Class cache = Integer.class.getDeclaredClasses()[0]: Field ...