快速入门理解Mybatis——自定义框架实现数据库查询操作

3.5 解析XML的工具类介绍


package mybatis.utils;

import mybatis.io.Resources;

import mybatis.cfg.Configuration;

import mybatis.cfg.Mapper;

import org.dom4j.Attribute;

import org.dom4j.Document;

import org.dom4j.Element;

import org.dom4j.io.SAXReader;

import java.io.IOException;

import java.io.InputStream;

import java.lang.reflect.Method;

import java.lang.reflect.ParameterizedType;

import java.lang.reflect.Type;

import java.util.HashMap;

import java.util.List;

import java.util.Map;

/**

  • 用于解析配置文件

*/

public class XMLConfigBuilder {

/**

  • 解析主配置文件,把里面的内容填充到DefaultSqlSession所需要的地方

  • 使用的技术:

  •  dom4j+xpath
    

*/

public static Configuration loadConfiguration(InputStream config){

try{

//定义封装连接信息的配置对象(mybatis的配置对象)

Configuration cfg = new Configuration();

//1.获取SAXReader对象

SAXReader reader = new SAXReader();

//2.根据字节输入流获取Document对象

Document document = reader.read(config);

//3.获取根节点

Element root = document.getRootElement();

//4.使用xpath中选择指定节点的方式,获取所有property节点

List propertyElements = root.selectNodes(“//property”);

//5.遍历节点

for(Element propertyElement : propertyElements){

//判断节点是连接数据库的哪部分信息

//取出name属性的值

String name = propertyElement.attributeValue(“name”);

if(“driver”.equals(name)){

//表示驱动

//获取property标签value属性的值

String driver = propertyElement.attributeValue(“value”);

cfg.setDriver(driver);

}

if(“url”.equals(name)){

//表示连接字符串

//获取property标签value属性的值

String url = propertyElement.attributeValue(“value”);

cfg.setUrl(url);

}

if(“username”.equals(name)){

//表示用户名

//获取property标签value属性的值

String username = propertyElement.attributeValue(“value”);

cfg.setUsername(username);

}

if(“password”.equals(name)){

//表示密码

//获取property标签value属性的值

String password = propertyElement.attributeValue(“value”);

cfg.setPassword(password);

}

}

//取出mappers中的所有mapper标签,判断他们使用了resource还是class属性

List mapperElements = root.selectNodes(“//mappers/mapper”);

//遍历集合

for(Element mapperElement : mapperElements){

//判断mapperElement使用的是哪个属性

Attribute attribute = mapperElement.attribute(“resource”);

if(attribute != null){

System.out.println(“使用的是XML”);

//表示有resource属性,用的是XML

//取出属性的值

String mapperPath = attribute.getValue();//获取属性的值"dao/IUserDao.xml"

//把映射配置文件的内容获取出来,封装成一个map

Map<String,Mapper> mappers = loadMapperConfiguration(mapperPath);

//给configuration中的mappers赋值

cfg.setMappers(mappers);

}

}

//返回Configuration

return cfg;

}catch(Exception e){

throw new RuntimeException(e);

}finally{

try {

config.close();

}catch(Exception e){

e.printStackTrace();

}

}

}

/**

  • 根据传入的参数,解析XML,并且封装到Map中

  • @param mapperPath 映射配置文件的位置

  • @return map中包含了获取的唯一标识(key是由dao的全限定类名和方法名组成)

  •      以及执行所需的必要信息(value是一个Mapper对象,里面存放的是执行的SQL语句和要封装的实体类全限定类名)
    

*/

private static Map<String,Mapper> loadMapperConfiguration(String mapperPath)throws IOException {

InputStream in = null;

try{

//定义返回值对象

Map<String,Mapper> mappers = new HashMap<String,Mapper>();

//1.根据路径获取字节输入流

in = Resources.getResourceAsStream(mapperPath);

//2.根据字节输入流获取Document对象

SAXReader reader = new SAXReader();

Document document = reader.read(in);

//3.获取根节点

Element root = document.getRootElement();

//4.获取根节点的namespace属性取值

String namespace = root.attributeValue(“namespace”);//是组成map中key的部分

//5.获取所有的select节点

List selectElements = root.selectNodes(“//select”);

//6.遍历select节点集合

for(Element selectElement : selectElements){

//取出id属性的值 组成map中key的部分

String id = selectElement.attributeValue(“id”);

//取出resultType属性的值 组成map中value的部分

String resultType = selectElement.attributeValue(“resultType”);

//取出文本内容 组成map中value的部分

String queryString = selectElement.getText();

//创建Key

String key = namespace+“.”+id;

//创建Value

Mapper mapper = new Mapper();

mapper.setQueryString(queryString);

mapper.setResultType(resultType);

//把key和value存入mappers中

mappers.put(key,mapper);

}

return mappers;

}catch(Exception e){

throw new RuntimeException(e);

}finally{

in.close();

}

}

}

需要在 pom.xml 中导入 dom4j、jaxen

dom4j

dom4j

1.6.1

jaxen

jaxen

1.1.6

3.5.1 在 mybatis.cfg 包下创建Configuration类和Mapper类

Configuration类中存储了"dao.UserDao.findAll"与一个Mapper对象的映射

Mapper对象中封装 执行的SQL语句 以及 结果类型的全限定类名

由于可能有多条sql语句,所以Configuration中的setMappers方法应该不断地往map中追加数据,而不是替换,使用 putAll 追加数据时必须先把map new出来,不然空指针。

package mybatis.cfg;

import java.util.Map;

public class Configuration {

private String driver;

private String url;

private String username;

private String password;

private Map<String, Mapper> mappers = new HashMap<String, Mapper>();

public String getDriver() {

return driver;

}

public void setDriver(String driver) {

this.driver = driver;

}

public String getUrl() {

return url;

}

public void setUrl(String url) {

this.url = url;

}

public String getUsername() {

return username;

}

public void setUsername(String username) {

this.username = username;

}

public String getPassword() {

return password;

}

public void setPassword(String password) {

this.password = password;

}

public Map<String, Mapper> getMappers() {

return mappers;

}

public void setMappers(Map<String, Mapper> mappers) {

this.mappers.putAll(mappers); // 追加方式添加至map

}

}

package mybatis.cfg;

/**

  • 用于封装 执行的SQL语句 以及 结果类型的全限定类名

*/

public class Mapper {

private String queryString;

private String resultType; // 实体类全限定类名

public String getQuerryString() {

return queryString;

}

public void setQueryString(String querryString) {

this.queryString = querryString;

}

public String getResultType() {

return resultType;

}

public void setResultType(String resultType) {

this.resultType = resultType;

}

}

3.6 SqlSessionFactoryBuilder 类的实现


package mybatis.sqlsession;

import mybatis.cfg.Configuration;

import mybatis.sqlsession.defaults.DefaultSqlSessionFactory;

import mybatis.utils.XMLConfigBuilder;

import java.io.InputStream;

/**

  • 用于创建SqlSessionFactory对象

*/

public class SqlSessionFactoryBuilder {

/**

  • 根据参数的字节输入流构建一个SqlSessionFactory工厂

  • @param in

  • @return

*/

public SqlSessionFactory build(InputStream config){

Configuration cfg = XMLConfigBuilder.loadConfiguration(config);

return new DefaultSqlSessionFactory(cfg);

}

}

SqlSessionFactoryBuilder 类中有一个 public SqlSessionFactory build(InputStream config)方法,需要返回一个SqlSessionFactory的对象。

3.6.1 SqlSessionFactory 接口的实现类

package mybatis.sqlsession.defaults;

import mybatis.cfg.Configuration;

import mybatis.sqlsession.SqlSession;

import mybatis.sqlsession.SqlSessionFactory;

/**

  • SqlSessionFactory 接口的实现

*/

public class DefaultSqlSessionFactory implements SqlSessionFactory {

private Configuration cfg;

public DefaultSqlSessionFactory(Configuration cfg){

this.cfg = cfg;

}

/**

  • 创建一个新的操作数据库对象

  • @return

*/

public SqlSession openSession() {

return new DefaultSqlSession(cfg);

}

}

SqlSessionFactory 中有一个 public SqlSession openSession() 方法,需要返回一个SqlSession的对象。

3.6.2 SqlSession 接口的实现类

package mybatis.sqlsession.defaults;

import mybatis.cfg.Configuration;

import mybatis.sqlsession.SqlSession;

import mybatis.sqlsession.proxy.MapperProxy;

import mybatis.utils.DataSourceUtil;

import javax.sql.DataSource;

import java.lang.reflect.Proxy;

import java.sql.Connection;

public class DefaultSqlSession implements SqlSession {

private Configuration cfg;

private Connection connection;

public DefaultSqlSession(Configuration cfg){

this.cfg = cfg;

connection = DataSourceUtil.getConnection(cfg);

}

/**

  • 创建代理对象

  • @param daoInterfaceClass dao的接口字节码

  • @param

  • @return

*/

public T getMapper(Class daoInterfaceClass) {

// 使用代理

// 被代理对象的类加载器

// 相同的接口

// 如何代理

return (T) Proxy.newProxyInstance(daoInterfaceClass.getClassLoader(),

new Class[]{daoInterfaceClass},

new MapperProxy(cfg.getMappers(), connection));

}

/**

  • 释放资源

*/

public void close() {

if(connection != null){

try{

connection.close();

}catch (Exception e){

e.printStackTrace();

}

}

}

}

DefaultSqlSession 中使用到的工具类 DataSourceUtil 为:

package mybatis.utils;

import mybatis.cfg.Configuration;

import java.sql.Connection;

import java.sql.DriverManager;

/**

  • 创建数据源的工具类

*/

public class DataSourceUtil {

public static Connection getConnection(Configuration cfg){

try{

// 注册驱动

Class.forName(cfg.getDriver());

return DriverManager.getConnection(cfg.getUrl(), cfg.getUsername(), cfg.getPassword());

}catch (Exception e){

throw new RuntimeException(e);

}

}

}

该类的 public T getMapper(Class daoInterfaceClass) 方法通过代理模式来实现

public static Object newProxyInstance(ClassLoader loader,

Class<?>[] interfaces,

InvocationHandler h)

loader: 用哪个类加载器去加载代理对象

interfaces:动态代理类需要实现的接口

h:动态代理方法在执行时,会调用h里面的invoke方法去执行

需要定义一个类 MapperProxy 来作为上面的h,该类必须实现 InvocationHandler 接口:

package mybatis.sqlsession.proxy;

import mybatis.cfg.Mapper;

import mybatis.utils.Executor;

import java.lang.reflect.InvocationHandler;

import java.lang.reflect.Method;

import java.sql.Connection;

import java.util.Map;

public class MapperProxy implements InvocationHandler {

// key : 全限定类名 + 方法名

private Map<String, Mapper> mappers;

private Connection conn;

public MapperProxy(Map<String, Mapper> mappers, Connection conn){

this.mappers = mappers;

this.conn = conn;

}

// 对方法进行增强,其实就是调用selectList方法

@Override

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

String methodName = method.getName();

// 获取方法所在类的名称

String className = method.getDeclaringClass().getName();

String key = className + “.” + methodName;

Mapper mapper = mappers.get(key);

if (mapper == null){

throw new IllegalArgumentException(“传入的参数有误”);

}

// 调用工具类查询所有

return new Executor().selectList(mapper, conn);

}

}

MapperProxy 中的使用到的工具类 Execute 为(中间定义了selectList方法):

package mybatis.utils;

import mybatis.cfg.Mapper;

import java.beans.PropertyDescriptor;

import java.lang.reflect.Method;

import java.sql.Connection;

import java.sql.PreparedStatement;

import java.sql.ResultSet;

import java.sql.ResultSetMetaData;

import java.util.ArrayList;

import java.util.List;

/**

  • 负责执行SQL语句,并且封装结果集

*/

public class Executor {

public List selectList(Mapper mapper, Connection conn) {

PreparedStatement pstm = null;

ResultSet rs = null;

try {

//1.取出mapper中的数据

String queryString = mapper.getQueryString();//select * from user

String resultType = mapper.getResultType();//com.itheima.domain.User

Class domainClass = Class.forName(resultType);

//2.获取PreparedStatement对象

pstm = conn.prepareStatement(queryString);

//3.执行SQL语句,获取结果集

rs = pstm.executeQuery();

//4.封装结果集

List list = new ArrayList();//定义返回值

while(rs.next()) {

//实例化要封装的实体类对象

E obj = (E)domainClass.newInstance();

//取出结果集的元信息:ResultSetMetaData

ResultSetMetaData rsmd = rs.getMetaData();

//取出总列数

int columnCount = rsmd.getColumnCount();

//遍历总列数

for (int i = 1; i <= columnCount; i++) {

//获取每列的名称,列名的序号是从1开始的

String columnName = rsmd.getColumnName(i);

//根据得到列名,获取每列的值

Object columnValue = rs.getObject(columnName);

//给obj赋值:使用Java内省机制(借助PropertyDescriptor实现属性的封装)

PropertyDescriptor pd = new PropertyDescriptor(columnName,domainClass);//要求:实体类的属性和数据库表的列名保持一种

//获取它的写入方法

Method writeMethod = pd.getWriteMethod();

//把获取的列的值,给对象赋值

writeMethod.invoke(obj,columnValue);

}

//把赋好值的对象加入到集合中

list.add(obj);

}

return list;

} catch (Exception e) {

throw new RuntimeException(e);

} finally {

release(pstm,rs);

}

}

private void release(PreparedStatement pstm,ResultSet rs){

if(rs != null){

try {

rs.close();

}catch(Exception e){

e.printStackTrace();

}

}

if(pstm != null){

try {

pstm.close();

}catch(Exception e){

e.printStackTrace();

}

}

}

}

3.7 基于注解的查询所有


修改 resources.SqlMapConfig.xml 中的语法为注解相应的形式:

dao.UserDao 添加注解:

@Select(“select * from user”)

List findAll();

新建一个 mybatis.annotations.Select 注解:

package mybatis.annotations;

import java.lang.annotation.ElementType;

import java.lang.annotation.Retention;

import java.lang.annotation.RetentionPolicy;

import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)

@Target(ElementType.METHOD)

public @interface Select {

/**

  • 配置sql语句

*/

String value();

}

utils.XMLConfigBuilder 中添加根据传入的参数,得到dao中所有被select注解标注的方法:

基于反射的原理,传入一个全限定类名,再通过这个类名获取字节码对象、方法,将带有注解的方法的相关信息加入到Mapper对象中。

/**

  • 根据传入的参数,得到dao中所有被select注解标注的方法。

  • 根据方法名称和类名,以及方法上注解value属性的值,组成Mapper的必要信息

  • @param daoClassPath

  • @return

*/

private static Map<String,Mapper> loadMapperAnnotation(String daoClassPath)throws Exception{

//定义返回值对象

Map<String,Mapper> mappers = new HashMap<String, Mapper>();

//1.得到dao接口的字节码对象

小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Java工程师,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新Java开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img
img

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频

如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Java)
img

最后

针对最近很多人都在面试,我这边也整理了相当多的面试专题资料,也有其他大厂的面经。希望可以帮助到大家。

下面的面试题答案都整理成文档笔记。也还整理了一些面试资料&最新2021收集的一些大厂的面试真题(都整理成文档,小部分截图)

在这里插入图片描述

最新整理电子书

在这里插入图片描述

/

String value();

}

utils.XMLConfigBuilder 中添加根据传入的参数,得到dao中所有被select注解标注的方法:

基于反射的原理,传入一个全限定类名,再通过这个类名获取字节码对象、方法,将带有注解的方法的相关信息加入到Mapper对象中。

/**

  • 根据传入的参数,得到dao中所有被select注解标注的方法。

  • 根据方法名称和类名,以及方法上注解value属性的值,组成Mapper的必要信息

  • @param daoClassPath

  • @return

*/

private static Map<String,Mapper> loadMapperAnnotation(String daoClassPath)throws Exception{

//定义返回值对象

Map<String,Mapper> mappers = new HashMap<String, Mapper>();

//1.得到dao接口的字节码对象

小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Java工程师,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新Java开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
[外链图片转存中…(img-phLra4h3-1711035706768)]
[外链图片转存中…(img-65dh6HG7-1711035706769)]
[外链图片转存中…(img-kEZUUbut-1711035706769)]

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频

如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Java)
[外链图片转存中…(img-aNdsyplg-1711035706770)]

最后

针对最近很多人都在面试,我这边也整理了相当多的面试专题资料,也有其他大厂的面经。希望可以帮助到大家。

下面的面试题答案都整理成文档笔记。也还整理了一些面试资料&最新2021收集的一些大厂的面试真题(都整理成文档,小部分截图)

[外链图片转存中…(img-O5Jm5kjh-1711035706770)]

最新整理电子书

[外链图片转存中…(img-WjMixWMG-1711035706770)]

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

  • 9
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
MyBatis Plus是一个开源的MyBatis增强工具,它提供了很多便捷的操作接口,包括实现分页操作。但是在某些情况下,我们需要用到自定义的SQL语句实现分页操作MyBatis Plus也提供了这样的功能。 首先,我们需要在Mapper接口中定义自定义SQL语句的方法,例如: ```java @Select("SELECT * FROM user WHERE age > #{age}") List<User> selectByAge(@Param("age") int age, Page<User> page); ``` 然后,在service层调用该方法时,需要使用MyBatis Plus提供的Page工具类来构建分页参数,例如: ```java Page<User> page = new Page<>(1, 10); // 构建分页参数 List<User> userList = userService.selectByAge(18, page); // 调用自定义SQL方法 page.setRecords(userList); // 构建分页结果 return page; ``` 其中,构建分页参数时,第一个参数为当前页数,第二个参数为每页数量。调用自定义SQL方法时,需要将Page对象作为参数传入。最后,我们可以将查询结果设置到Page对象中,构建完整的分页结果。 值得注意的是,使用自定义的SQL语句实现分页操作时,需要按照MyBatis Plus的分页规则来编写SQL语句,例如在SELECT语句中使用LIMIT关键字实现分页。同时,需要避免使用ORDER BY语句,在SQL语句中执行排序操作,以保证分页功能的性能。 综上所述,MyBatis Plus提供了很多便捷的操作接口,但是在某些情况下,我们需要用到自定义的SQL语句实现操作MyBatis Plus也提供了这样的功能,只需要按照规则编写SQL语句,并将Page对象作为参数传入自定义SQL方法即可实现分页操作

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值