这篇文章给大家分享的是有关Mybatis怎么实现动态增删改查功能的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。
最近在看 Mybatis 的源码,大致了解整个框架流程后便手写了一个特别简单的SimpMybatis
的小Demo,来巩固这整个框架的学习。下图是我所画的框架大致执行流程:
对上图分析后得出结论:
1.Mybatis 的配置文件分为两种,且这两个配置文件会被封装到 Configuration 中
主配置文件(MybatisConfig.xml):配置 jdbc 等环境信息,全局唯一;
映射文件(xxxMapper.xml):配置多个 Sql ,可有多个。
2.通过 Mybatis 配置文件得到 SqlSessionFactory ;
3.通过 SqlSessionFactory 得到 SqlSession,它就相当于 Request 请求;
4.SqlSession 调用底层的 Executor 执行器来操作数据库,同时执行器有两类实现
基本实现
带有缓存功能的实现
5.解析传入的参数,对其进行封装,执行并返回结果;
以上就是我梳理的 Mybatis 大致流程,看似简单,却很精妙。
从图中可以看出,MyConfig 负责与人交互。待读取xml后,将属性和连接数据库的操作封装在 MyConfig 对象中供后面的组件调用。本项目将使用 dom4j 来读取xml文件,它具有性能优异和非常方便使用的特点。
从流程图中的箭头可以看出,MySqlSession 的成员变量中必须得有 MyExecutorImpl 和 MyConfig 去集中做调配。一个Session仅拥有一个对应的数据库连接。类似于一个前段请求Request,它负责直接调用对应 execute(sql) 来做 CRUD 操作。
MyExecutor 是一个执行器,负责SQL语句的生成和查询缓存的维护,也就是 Jdbc 的代码将在这里完成,不过本文只实现了单表,查询缓存并未实现。
只是希望对指定的接口生成一个对象,使得执行它的时候能运行一句 sql,而接口无法直接调用方法,所以这里使用动态代理生成对象,在执行时还是回到 MySqlSession 中调用查询,最终由 MyExecutorImpl 做 JDBC查询。这样设计是为了单一职责,可扩展性更强。
这次会将其打成 Jar 包,并将其导入项目实现,做一个 Mybatis 的还原。
工程文件及目录:
Maven 导入如下:
<!-- https://mvnrepository.com/artifact/org.dom4j/dom4j -->
<!-- xml解析 -->
<dependency>
<groupId>org.dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>2.1.3</version>
</dependency>
<!-- Mysql -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.49</version>
</dependency>
/**
* @author Kenelm
* @version 1.0
* @date 2020/11/22 12:17
*/
public class MyConfig {
/**
* 启动应用程序类加载器
*/
private static final ClassLoader loader = ClassLoader.getSystemClassLoader();
/**
* 数据库建立连接
* @return 返回数据库连接对象
*
*/
public Connection build() {
// Mybatis主配置文件名
String resource = "mybatis-config.xml";
// 获取文件根节点
Element root = parseXML(resource);
// 获取文件对应的信息
Map<String, String> jdbcMap = parseNodes(root);
try {
Class.forName(jdbcMap.get("driverClassName"));
} catch (ClassNotFoundException e) {
throw new RuntimeException("驱动器未找到,请重新检查!");
}
Connection connect = null;
try {
connect = DriverManager.getConnection(jdbcMap.get("url"), jdbcMap.get("username"), jdbcMap.get("password"));
} catch (SQLException throwables) {
throw new RuntimeException("数据库连接错误,请检查路径、用户名、密码是否输入正确!");
}
return connect;
}
/**
* 解析数据库配置文件
* @param resource 数据库配置文件路径
* @return 获取到的文件根节点
*/
public static Element parseXML(String resource) {
try {
// 返回用于读取指定资源的输入流
InputStream stream = loader.getResourceAsStream(resource);
// 使用dom4j解析XML
SAXReader reader = new SAXReader();
// 使用SAX从给定流中读取文件
Document doc = reader.read(stream);
// 获取文件的根节点
return doc.getRootElement();
} catch (DocumentException e) {
throw new RuntimeException("解析 XML 时发生错误!" + resource);
}
}
/**
* 解析主xml文件标签节点
* @param node 配置文件根节点
* @return 返回从配置文件中拿到的开启数据库对应值
*/
private Map<String, String> parseNodes(Element node) {
// 判断根标签名称
if (!node.getName().equals("database")) {
throw new RuntimeException("数据库配置文件根标签名称必须为【database】");
}
// 存放配置文件取得的值
Map<String, String> map = new HashMap<String, String>();
map.put("driverClassName", null);
map.put("url", null);
map.put("username", null);
map.put("password", null);
// 读取property的属性内容
for (Element item : node.elements()) {
// 获取标签中存放的值,并删除其前导和结尾的空格
String value = getValue(item);
// 获取标签中 name 的名称
String name = item.attributeValue("name");
// 如果name或value为空则有对应值未输入
if (name == null || "".equals(value)) {
throw new RuntimeException("[database]: <property> 中应该包含名称和值");
}
switch (name) {
case "driverClassName" : map.put("driverClassName", value); break;
case "url" : map.put("url", value); break;
case "username" : map.put("username", value); break;
case "password" : map.put("password", value); break;
default: throw new RuntimeException("[database]: <property> 中有未知属性");
}
}
return map;
}
/**
* 获取property属性中的值
* @param node 配置文件根节点
* @return 如果有value值,则读取;没有设置value,则读取内容
*/
private static String getValue(Element node) {
return node.hasContent() ? node.getText().trim() : node.attributeValue("value").trim();
}
/**
*
* @param path
* @return
*/
@SuppressWarnings(value = "rawtypes")
public MappingBean readMapper(String path) {
MappingBean bean = new MappingBean();
try {
InputStream stream = loader.getResourceAsStream(path);
SAXReader reader = new SAXReader();
Document doc = reader.read(stream);
Element root = doc.getRootElement();
// 把mapper节点的nameSpace值存为接口名
bean.setInterfaceName(root.attributeValue("nameSpace").trim());
// 用来存储方法的List
List<Mapping> list = new ArrayList<Mapping>();
//遍历根节点下所有子节点
for(Iterator rootIter = root.elementIterator(); rootIter.hasNext();) {
// 存储一条方法的信息
Mapping fun = new Mapping();
Element e = (Element) rootIter.next();
String sqlType = e.getName().trim();
String funcName = e.attributeValue("id").trim();
String sql = e.getText().trim();
String resultType = e.attributeValue("resultType").trim();
fun.setSqlType(sqlType);
fun.setFuncName(funcName);
Object newInstance = null;
try {
newInstance = Class.forName(resultType).newInstance();
} catch (InstantiationException | IllegalAccessException | ClassNotFoundException e1) {
e1.printStackTrace();
}
fun.setResultType(newInstance);
fun.setSql(sql);
list.add(fun);
}
bean.setList(list);
} catch (DocumentException e) {
e.printStackTrace();
}
return bean;
}
/**
* 解析mapper映射xml文件
* @param element mapper文件路径
* @return
*/
public MappingBean parseMapper(Element element) {
MappingBean bean = new MappingBean();
String namespace = element.attributeValue("namespace");
if (namespace == null) {
throw new RuntimeException("映射文件namespace不存在");
}
bean.setInterfaceName(namespace);
List<Mapping> list = new ArrayList<>();
Iterator<Element> it = element.elementIterator();
while (it.hasNext()) {
Element ele=(Element) it.next();
Mapping mapping =new Mapping();
String funcName =ele.attributeValue("id");
if (funcName==null){
throw new RuntimeException("mapper映射文件中id不存在");
}
String sqlType = ele.getName();
String paramType = ele.attributeValue("parameterType");
String resultType=ele.attributeValue("resultType");
String sql=ele.getText().trim();
mapping.setFuncName(funcName);
mapping.setSqlType(sqlType);
mapping.setParameterType(paramType);
mapping.setSql(sql);
Object object=null;
try {
object=Class.forName(resultType).newInstance();
} catch (InstantiationException | IllegalAccessException | ClassNotFoundException e) {
e.printStackTrace();
}
mapping.setResultType(object);
list.add(mapping);
}
bean.setList(list);
return bean;
}
}
⚡由 MyConfig类 代码可以得知:
Mybatis 主配置类名称必须为:mybatis-config.xml
;
mybatis-config.xml 的根标签必须为: <database></database>
;
Mapper.xml 必须包括:namespace
;
Sql 是否有返回值都应包括:resultType
(个人偷懒,没做判断);... ...
MySqlSession 肯定不会自己去执行,因为不能写死所以使用动态代理来使代理类去实现具体方法。
/**
* @author Kenelm
* @version 1.0
* @date 2020/11/22 12:52
*/
public class MySqlSession {
private final MyExcutor excutor= new MyExcutorImpl();
private final MyConfig config = new MyConfig();
public <T> T selectValue(Mapping statement, List<Object> parameter){
return excutor.queryValue(statement, parameter);
}
public <T> T selectNull(Mapping statement){
return excutor.queryNull(statement);
}
public int deleteValue(Mapping statement, List<Object> parameter) {
return excutor.deleteValue(statement, parameter);
}
public int updateValue(Mapping statement, List<Object> parameter) {
return excutor.updateValue(statement, parameter);
}
public int insertValue(Mapping mapping, List<Object> parameter) {
return excutor.insertValue(mapping, parameter);
}
@SuppressWarnings("unchecked")
public <T> T getMapper(Class<T> clas){
//动态代理调用
return (T) Proxy.newProxyInstance(clas.getClassLoader(),new Class[]{clas},
new MySqlSessionProxy(config,this));
}
}
编写代理类,把mapper映射文件解析进来
/**
* @author Kenelm
* @version 1.0
* @date 2020/11/22 12:55
*/
public class MySqlSessionProxy implements InvocationHandler {
private MyConfig config;
private MySqlSession sqlSession;
public MySqlSessionProxy(MyConfig config, MySqlSession sqlSession) {
this.config = config;
this.sqlSession = sqlSession;
}
@Override
public Object invoke(Object proxy, Method method,Object[] args) {
String name = method.getDeclaringClass().getName();
String mapperName = name.substring(name.lastIndexOf(".")+1);
MappingBean bean=config.parseMapper(MyConfig.parseXML(mapperName+".xml"));
if (bean!=null && (bean.getList()!=null && bean.getList().size()>0)){
for (Mapping mapping : bean.getList()){
if (mapping.getFuncName().equals(method.getName())) {
// 判断是否为查询语句
if ("select".equals(mapping.getSqlType().toLowerCase())) {
System.out.println("执行查询方法:" + mapping.getSql());
if (args!=null) {
System.out.println("参数:"+ Arrays.toString(args));
return sqlSession.selectValue(mapping, Arrays.asList(args));
} else {
System.out.println("参数:null");
return sqlSession.selectNull(mapping);
}
}
// 判断是否为删除语句
if ("delete".equals(mapping.getSqlType().toLowerCase())){
System.out.println("执行查询方法:"+mapping.getSql());
System.out.println("参数:"+ Arrays.toString(args));
return sqlSession.deleteValue(mapping, Arrays.asList(args));
}
// 判断是否为更新语句
if ("update".equals(mapping.getSqlType().toLowerCase())) {
System.out.println("执行查询方法:"+mapping.getSql());
System.out.println("参数:"+ Arrays.toString(args));
return sqlSession.updateValue(mapping, Arrays.asList(args));
}
// 判断是否为插入语句
if ("insert".equals(mapping.getSqlType().toLowerCase())) {
System.out.println("执行查询方法:" + mapping.getSql());
System.out.println("参数:" + Arrays.toString(args));
return sqlSession.insertValue(mapping, Arrays.asList(args));
}
}
}
}
return null;
}
}
⚡注意:通过上段代码可知,映射文件必须和接口名称保持一致。
a. 接口实体类
/**
* @author Kenelm
* @version 1.0
* @date 2020/11/22 12:38
*/
public class MappingBean {
/**
* 接口名
*/
private String interfaceName;
/**
* 接口下所有方法
*/
private List<Mapping> list;
// setter、getter略
}
b. 映射文件中 Sql 的实体类
/**
* @author Kenelm
* @version 1.0
* @date 2020/11/22 12:38
*/
public class Mapping {
private String sqlType;
private String funcName;
private String sql;
private Object resultType;
private String parameterType;
// setter、getter略
}
MyExcutor 接口
/**
* @author Kenelm
* @version 1.0
* @date 2020/11/22 12:42
*/
public interface MyExcutor {
// 无参查询
<T> T queryNull(Mapping mapping);
// 有参查询
<T> T queryValue(Mapping mapping, List<Object> params);
// 删除
int deleteValue(Mapping mapping, List<Object> params);
// 更新
int updateValue(Mapping mapping, List<Object> params);
// 插入
int insertValue(Mapping mapping, List<Object> params);
}
MyExcutorImpl 实现类
这里通过反射将结果转换成对象
/**
* @author Kenelm
* @version 1.0
* @date 2020/11/22 12:42
*/
public class MyExcutorImpl implements MyExcutor {
private MyConfig config = new MyConfig();
@Override
public <T> T queryNull(Mapping mapping) {
Connection conn = config.build();
PreparedStatement preparedStatement;
ResultSet resultSet;
Object obj;
List<Object> list = new ArrayList<>();
try {
preparedStatement=conn.prepareStatement(mapping.getSql());
if (mapping.getResultType() == null){
throw new RuntimeException("返回的映射结果不能为空!");
}
resultSet = preparedStatement.executeQuery();
int row = 0;
ResultSetMetaData rd = resultSet.getMetaData();
while (resultSet.next()){
obj=resultToObject(resultSet,mapping.getResultType());
row++;
list.add(obj);
}
System.out.println("记录行数:"+row);
} catch (SQLException e) {
e.printStackTrace();
}
return (T) list;
}
@Override
public <T> T queryValue(Mapping mapping, List<Object> params) {
Connection conn = config.build();
PreparedStatement preparedStatement;
ResultSet resultSet;
Object obj;
List<Object> list = new ArrayList<>();
try {
preparedStatement=conn.prepareStatement(mapping.getSql());
for (int i=0; i<params.size(); i++) {
preparedStatement.setString(i+1, params.get(i).toString());
}
if (mapping.getResultType() == null){
throw new RuntimeException("返回的映射结果不能为空!");
}
resultSet = preparedStatement.executeQuery();
int row = 0;
ResultSetMetaData rd = resultSet.getMetaData();
while (resultSet.next()){
obj=resultToObject(resultSet,mapping.getResultType());
row++;
list.add(obj);
}
System.out.println("记录行数:"+row);
} catch (SQLException e) {
e.printStackTrace();
}
return (T) list;
}
@Override
public int deleteValue(Mapping mapping, List<Object> params) {
Connection conn = config.build();
int rows = 0;
PreparedStatement preparedStatement=null;
try {
preparedStatement = conn.prepareStatement(mapping.getSql());
for (int i=0; i<params.size(); i++) {
preparedStatement.setString(i+1, params.get(i).toString());
}
rows = preparedStatement.executeUpdate();
if (rows != 0) {
System.out.println("删除成功,受影响行数:"+rows);
} else {
System.out.println("删除失败,数据库无相应数据...");
}
} catch (SQLException e) {
e.printStackTrace();
}
return rows;
}
@Override
public int updateValue(Mapping mapping, List<Object> params) {
Connection conn = config.build();
int rows = 0;
PreparedStatement preparedStatement=null;
try {
preparedStatement = conn.prepareStatement(mapping.getSql());
for (int i=0; i<params.size(); i++) {
preparedStatement.setString(i+1, params.get(i).toString());
}
rows = preparedStatement.executeUpdate();
if (rows != 0) {
System.out.println("修改成功,受影响行数:"+rows);
} else {
System.out.println("修改失败,数据库无相应数据...");
}
} catch (SQLException e) {
e.printStackTrace();
}
return rows;
}
@Override
public int insertValue(Mapping mapping, List<Object> params) {
Connection conn = config.build();
int rows = 0;
PreparedStatement preparedStatement=null;
try {
preparedStatement = conn.prepareStatement(mapping.getSql());
for (int i=0; i<params.size(); i++) {
preparedStatement.setString(i+1, params.get(i).toString());
}
try {
rows = preparedStatement.executeUpdate();
if (rows != 0) {
System.out.println("插入成功,受影响行数:"+rows);
} else {
System.out.println("插入失败...");
}
} catch (SQLException throwables) {
throw new RuntimeException("插入重复 \"Key\" 值数据");
}
} catch (SQLException e) {
e.printStackTrace();
}
return rows;
}
private <T> T resultToObject(ResultSet rs, Object object) {
Object obj=null;
try {
Class<?> cls = object.getClass();
/*
这里为什么要通过class再new一个对象?
因为如果不new一个新的对象,每次返回的都是形参上的object,
而这个object都是同一个,会导致list列表后面覆盖前面值。
*/
obj=cls.newInstance();
//获取结果集元数据(获取此 ResultSet 对象的列的编号、类型和属性。)
ResultSetMetaData rd=rs.getMetaData();
for (int i = 0; i < rd.getColumnCount(); i++) {
//获取列名
String columnName=rd.getColumnLabel(i+1);
//组合方法名
String methodName="set"+columnName.substring(0, 1).toUpperCase()+columnName.substring(1);
//获取列类型
int columnType=rd.getColumnType(i+1);
Method method=null;
switch(columnType) {
case java.sql.Types.VARCHAR:
case java.sql.Types.CHAR:
method=cls.getMethod(methodName, String.class);
method.invoke(obj, rs.getString(columnName));
break;
case java.sql.Types.INTEGER:
method=cls.getMethod(methodName, Integer.class);
method.invoke(obj, rs.getInt(columnName));
break;
default:
break;
}
}
} catch (IllegalAccessException | InstantiationException | NoSuchMethodException | InvocationTargetException | SQLException e) {
e.printStackTrace();
}
return (T) obj;
}
}
<!-- https://mvnrepository.com/artifact/org.dom4j/dom4j -->
<!-- xml解析 -->
<dependency>
<groupId>org.dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>2.1.3</version>
</dependency>
<!-- Mysql -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.49</version>
</dependency>
<!-- 自己写的Mybatis,首先要将其放入本地仓库 -->
<dependency>
<groupId>top.kk233</groupId>
<artifactId>SimpMybatis</artifactId>
<version>1.0.0</version>
</dependency>
⚡Maven导入本地Jar包方法自行百度,这里就不赘述。
这里提供一个我测试的,你们可以自行创建其他的
CREATE DATABASE IF NOT EXISTS `test`;
USE `test`;
CREATE TABLE `user` (
`id` INT ( 10 ) NOT NULL,
`sex` VARCHAR ( 2 ) NOT NULL,
`password` VARCHAR ( 255 ) DEFAULT NULL,
`username` VARCHAR ( 255 ) DEFAULT NULL,
PRIMARY KEY ( `id` )
) ENGINE = INNODB AUTO_INCREMENT = 2 DEFAULT CHARSET = utf8;
INSERT INTO `test`.`user` ( `id`, `sex`, `password`, `username` )
VALUES
( 1, '男', '12344', '五六' ),
( 2, '女', '12643', '张三' ),
( 3, '男', '1245453', '李四' );
/**
* @author Kenelm
* @version 1.0
* @date 2020/11/22 16:17
*/
public class User {
private Integer id;
private String sex;
private String password;
private String username;
// setter、getter略
}
/**
* @author Kenelm
* @version 1.0
* @date 2020/11/22 16:17
*/
public interface UserMapper {
List<User> getUsers();
List<User> getUserBySexAndName(String sex, String username);
int deleteUserById(Integer id);
int updateUserByName(String username, String password);
int insertUser(int id, String sex, String password, String username);
}
<?xml version="1.0" encoding="UTF-8"?>
<mapper namespace="top.kk233.mapper.UserMapper">
<select id="getUsers" resultType="top.kk233.pojo.User">
SELECT * FROM user
</select>
<select id="getUserBySexAndName" resultType="top.kk233.pojo.User">
select * from user where sex=? and username=?
</select>
<delete id="deleteUserById" resultType="top.kk233.pojo.User">
delete from user where id=?
</delete>
<update id="updateUserByName" resultType="top.kk233.pojo.User">
update user set password=? where username=?
</update>
<insert id="insertUser" resultType="top.kk233.pojo.User">
insert into user values(?,?,?,?)
</insert>
</mapper>
<?xml version="1.0" encoding="UTF-8"?>
<database>
<property name="driverClassName">com.mysql.jdbc.Driver</property>
<property name="url">jdbc:mysql://localhost:3306/test?useSSL=false</property>
<property name="username">root</property>
<property name="password">124760</property>
</database>
/**
* @author Kenelm
* @version 1.0
* @date 2020/11/22 16:24
*/
public class app {
public static void main(String[] args) {
MySqlSession sql = new MySqlSession();
UserMapper mapper = sql.getMapper(UserMapper.class);
List<User> users = mapper.getUsers();
users.forEach(System.out::println);
System.out.println("==========================");
List<User> users1 = mapper.getUserBySexAndName("女", "张三");
users1.forEach(System.out::println);
System.out.println("==========================");
mapper.deleteUserById(1);
System.out.println("==========================");
mapper.updateUserByName("五六", "女");
System.out.println("==========================");
mapper.insertUser(10, "男", "123123", "五七");
}
}
测试成功,这就是本人所手写的Mybatis,虽然比较简单,但还是学习到了很多东西。
项目放在 Gitee 上有需要自行下载,觉得可以还请点个Star
感谢各位的阅读!关于“Mybatis怎么实现动态增删改查功能”这篇文章就分享到这里了,希望以上内容可以对大家有一定的帮助,让大家可以学到更多知识,如果觉得文章不错,可以把它分享出去让更多的人看到吧!
亿速云「云服务器」,即开即用、新一代英特尔至强铂金CPU、三副本存储NVMe SSD云盘,价格低至29元/月。点击查看>>
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。