Mybatis概述加手写mybatis中的查询功能

1、Mybatis概述

1.1 Mybatis概念

         MyBatis 本是 apache 的一个开源项目 iBatis, 2010 年这个项目由 apache software foundation 迁移到了 google code,并且改名为 MyBatis 。2013 年 11 月迁移到 Github。iBATIS 一词来源于“internet”和“abatis”的组合,是一个基于 Java 的持久层框架。iBATIS 提供的持 久层框架包括 SQL Maps 和 Data Access Objects(DAO)。 Mybatis 基于java的持久层框架,它的内部封装了JDBC,让开发人员只需要关注SQL语句本身,不需要花费精力在驱动的加载、连接的创 建、Statement的创建等复杂的过程。 Mybatis通过XML或注解的方式将要执行的各种的statement配置起来,并通过java对象和statement中的sql的动态参数进行映射生成最终执 行的SQL语句,最后由mybatis框架执行SQL,并将结果直接映射为java对象。 采用了ORM思想解决了实体类和数据库表映射的问题。对JDBC进行了封装,屏蔽了JDBCAPI底层的访问细节,避免我们与jdbc的api打交 道,就能完成对数据的持久化操作。

O--Object java对象

R- Relation 关系,就是数据库中的一张表

M-mapping 映射

1.2 JDBC编程

通过反射实现对任意表的查询

public class JdbcUtil<E> {

    public Connection getConnection() throws ClassNotFoundException, SQLException {
        String url = "jdbc:mysql://localhost:3306/testdb?serverTimezone=Asia/Shanghai";
        String username = "root";
        String password = "123456";
        // 加载驱动程序
        Class.forName("com.mysql.cj.jdbc.Driver");
        // 建立数据库连接
        Connection connection = DriverManager.getConnection(url, username, password);
        return connection;
    }

    @SneakyThrows
    public  List<Object> query(String sql,Class<E> clazz){
        Connection connection = new JdbcUtil().getConnection();
        PreparedStatement pstmt = connection.prepareStatement(sql);
        ResultSet rs = pstmt.executeQuery();
        ResultSetMetaData md = rs.getMetaData();
        int columns = md.getColumnCount();
        // 获取默认构造函数
        Constructor<?> constructor = clazz.getDeclaredConstructor();
        List<Object> list = new ArrayList<>();
        while (rs.next()) {
            // 创建对象
            String setMethod = "set";
            Object object = constructor.newInstance();
            for (int i = 1; i <= columns; i++) {
                // 获取列名
                String columnName = md.getColumnName(i);
                // 获取属性的 setter 方法名
                String setterMethodName = "set" + getMethodName(columnName);
                // 获取属性的类型
                Class<?> parameterType = object.getClass().getDeclaredField(columnName).getType();
                // 获取属性的 setter 方法
                Method setterMethod = object.getClass().getMethod(setterMethodName, parameterType);
                // 获取结果集中的字段值
                Object value = rs.getObject(i);
                // 调用 setter 方法设置属性值
                setterMethod.invoke(object, value);
            }
            list.add(object);
        }
        //关闭资源和连接
        rs.close();
        pstmt.close();
        connection.close();
        return list;
    }

    public String getMethodName(String str){
        if (str == null || str.isEmpty()) {
            return str;
        }

        return Character.toUpperCase(str.charAt(0)) + str.substring(1);
    }
}

1.3 Mybatis解决的问题

1、数据库连接的创建、释放连接的频繁操作造成资源的浪费从而影响系统的性能。

2、SQL语句编写在代码中,硬编码造成代码不容易维护,实际应用中SQL语句变化的可能性比较大,一旦变动就需要改变java类。

3、使用preparedStatement的时候传递参数使用占位符,也存在硬编码,因为SQL语句变化,必须修改源码。

4、对结果集的解析中也存在硬编码。

2、Mybatis对象分析

2.1 Resources

Resources 类,顾名思义就是资源,用于读取资源文件。其有很多方法通过加载并解析资源文件,返回不同类型的 IO 流对象。

2.2 SqlSessionFactoryBuilder

SqlSessionFactory 的 创 建 , 需 要 使 用 SqlSessionFactoryBuilder 对 象 的 build() 方 法 。 事实上使用SqlSessionFactoryBuilder的原因 是将SqlSessionFactory这个复杂对象的创建交由Builder来执行,也就是使用了建造者设计模式。

建造者模式: 又称生成器模式,是一种对象的创建模式。 可以将一个产品的内部表象与产品的生成过程分割开来, 从而可以使一个建造过程生成具有 不同的内部表象的产品(将一个复杂对象的构建与它的表示分离, 使得同样的构建过程可以创建不同的表示). 这样用户只需指定需要建造的类型就可 以得到具体产品,而不需要了解具体的建造过程和细节. 在建造者模式中,角色分指导者(Director)与建造者(Builder): 用户联系指导者, 指导者指挥建造者, 最后得到产品. 建造者模式可以强制实行 一种分步骤进行的建造过程

2.3 SqlSessionFactory

SqlSessionFactory 接口对象是一个重量级对象(系统开销大的对象),是线程安全的,所以一个应用只需要一个该对象即可。创建 SqlSession 需要使用 SqlSessionFactory 接口的的 openSession()方法。

默认的 openSession()方法没有参数,它会创建有如下特性的 SqlSession: 1、会开启一个事务(也就是不自动提交)。 2、将从由当前环境配置的 DataSource 实例中获取 Connection 对象。事务隔离级别将会使用驱动或数据源的默认设置。 3、预处理语句不会被复用,也不会批量处理更新。 openSession(true):创建一个有自动提交功能的 SqlSession openSession(false):创建一个非自动提交功能的 SqlSession,需手动提交 openSession():同 openSession(false)

2.4 SqlSession

SqlSession 接口对象用于执行持久化操作。一个 SqlSession 对应着一次数据库会话,一次会话以SqlSession 对象的创建开始,以 SqlSession 对象的关闭结束。 SqlSession 接口对象是线程不安全的,所以每次数据库会话结束前,需要马上调用其 close()方法,将其关闭。再次需要会话,再次创建。 SqlSession 在方法内部创建,使用完毕后关闭。 SqlSession 类中有超过 20 个方法,我们常用的几乎都是执行语法相关的方法。 这些方法被用来执行定义在 SQL 映射的 XML 文件中的 SELECT、INSERT、UPDATE 和 DELETE 语句。它们都会自行解释,每一句都使用语 句的 ID 属性和参数对象,参数可以是原生类型(自动装箱或包装类)、JavaBean、POJO 或 Map。

<T> T selectOne(String statement, Object parameter)
<E> List<E> selectList(String statement, Object parameter)
<K,V> Map<K,V> selectMap(String statement, Object parameter, String mapKey)
int insert(String statement, Object parameter)
int update(String statement, Object parameter)
int delete(String statement, Object parameter)
<!--
selectOne 和 selectList 的不同仅仅是 selectOne 必须返回一个对象或 null 值。如果返回值多于一个,那么就会抛出异常。
selectMap 稍微特殊一点,因为它会将返回的对象的其中一个属性作为 key 值,将对象作为 value 值,从而将多结果集转为 Map 类型值。因为
并不是所有语句都需要参数,所以这些方法都重载成不需要参数的形式。
-->

3.5 Mybatis架构

1、Mybatis.xml文件是mybatis框架的全局配置文件,配置了mybatis框架运行的环境等信息Mapper1.xml.....是SQL的映射文件,文件中配置了所有的操作数据库的sql语句,这些文件需要在全局配置文件中加载。
2、通过mybatis环境等配置信息构建SqlSessionFactroy ,相当于是产生连接池
3、由会话工厂创建SqlSession即会话(连接),操作数据库需要通过SqlSession进行的。
4、Mybatis底层自定义了Executor执行器的接口操作数据库,Executor接口有两个实现,一个基本的执行器,一个是缓存的执行器。
5、Mapped statement 也是mybatis框架一个底层的封装对象,他包装了mybatis配置信息以及sql映射信息。Mapper.xml文件中的一个SQL语句对应一个Mapped statement对象,sql的id就是Mapped statement的id。
6、Mapped statement对SQL执行输入参数的定义,输入参数包括HashMap、基本类型、pojo,Executor通过Mapped statemen在执行SQL语句前将输入java对象映射到sql语句中,执行完毕SQL之后,输出映射就是JDBC编码中的对preparedStatement 执行结果的定义。

3.手写一个类似的mybatis中的一小段(查询)

在 MyBatis 中,动态代理技术用于自动生成 Mapper 接口的实现类,使开发者只需编写接口定义而无需实现具体的方法体。在这里,我们将通过注解的方式来模拟 MyBatis 读取 XML 配置文件的过程,并进行一些优化。

3.1首先,我们定义一个 @Select 注解,用于标记 Mapper 接口中的查询方法,并指定对应的 SQL 语句:

package com.fbatis.anno;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Retention(RetentionPolicy.RUNTIME)
public @interface Select {
    String value();
}

3.2接下来,我们创建一个 MapperProxy类,实现动态代理的逻辑:

package com.fbatis.proxy;

import com.fbatis.anno.Select;
import com.fbatis.session.Session;

import java.awt.*;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.util.Collection;
import java.util.List;

//数据库连接-Connection ===session
public class MapperProxy implements InvocationHandler {
    private final String openToken="#{";
    private final String closeToken="}";
    Session session;
    public MapperProxy(Session session){
        this.session=session;
    }

    //执行sql 返回相应数据给我们
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        /**
         * 1、拿到sql
         */
        if(method.isAnnotationPresent(Select.class)) {
            //当前方法的返回类型---listOne=MemberEntity.class
            Class<?> returnType = method.getReturnType();

            Select select = method.getAnnotation(Select.class);
            String sql  = select.value();
            //2、解析sql
            sql = parse(sql);

            //已经明确了需要执行 pstmt.executeQuery();
            //需要知道返回多少数据
            if(Collection.class.isAssignableFrom(returnType)){
                //需要返回list 一般会有泛型 所以第一步获取泛型的类型
                ParameterizedType parameterizedType = (ParameterizedType) method.getGenericReturnType();
                Class clazz = (Class) parameterizedType.getActualTypeArguments()[0];
                //需要把泛型类型传给执行sql那里---rs转化--entity--add--List
                List list = session.executeQuery(sql, clazz,args);
                return list;
            }else if(Integer.class.isAssignableFrom(returnType)){
                Integer count = (Integer) session.executeQuery(sql, returnType,args).get(0);
                return count;
            }else{
                List entity1 = session.executeQuery(sql, returnType, args);
                if(entity1.size()==0) {
                    return null;
                }
                Object entity = entity1.get(0);
                return entity;
            }

        }
        return null;
    }


    public String parse(String text) {
        if (text == null || text.isEmpty()) {
            return "";
        }
        int start = text.indexOf(openToken);
        if (start == -1) {
            return text;
        }
        char[] src = text.toCharArray();
        int offset = 0;
        final StringBuilder builder = new StringBuilder();
        StringBuilder expression = null;
        do {
            if (start > 0 && src[start - 1] == '\\') {
                // 这个开放标记被转义了。去掉反斜杠并继续
                builder.append(src, offset, start - offset - 1).append(openToken);
                offset = start + openToken.length();
            } else {
                // 找到了开放标记。让我们搜索闭合标记
                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);
                        break;
                    }
                }
                if (end == -1) {
                    // close token was not found.
                    builder.append(src, start, src.length - start);
                    offset = src.length;
                } else {
                    builder.append("?");
                    offset = end + closeToken.length();
                }
            }
            start = text.indexOf(openToken, offset);
        } while (start > -1);
        if (offset < src.length) {
            builder.append(src, offset, src.length - offset);
        }
        return builder.toString();
    }

}

在上述代码中,我们通过 method.getAnnotation(Select.class) 来获取方法上的 @Select 注解,并从注解中获取 SQL 语句。然后,我们执行 SQL 查询,并处理结果集。

3.3接下来,我们创建一个 类,用于创建连接池:

package com.fbatis.session;

import org.springframework.jdbc.datasource.DriverManagerDataSource;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;

/**
 * 1、创建session
 * 2、连接池
 */
public class SessionFactory {

    DataSource dataSource;

    public SessionFactory(){
        //创建连接池
        DriverManagerDataSource source = new DriverManagerDataSource();
        source.setDriverClassName("com.mysql.cj.jdbc.Driver");
        source.setUsername("root");
        source.setPassword("123456");
        source.setUrl("jdbc:mysql://localhost:3306/testdb?serverTimezone=Asia/Shanghai");
        dataSource=source;
    }

    public Session createSession(){
        Connection connection = null;
        try {
            connection = dataSource.getConnection();
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
        return  new Session(connection);
    }
}

3.4接下来,我们创建一个 MyBatisSqlSession 类,用于创建动态代理对象和创建连接:

package com.fbatis.session;

import com.fbatis.proxy.MapperProxy;
import com.fbatis.test.MemperDao;
import org.apache.commons.beanutils.BeanUtils;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Proxy;
import java.sql.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 1、getDao(Class<T> clazz)--返回对象
 * 2、数据库连接
 */
public class Session<T> {
    Connection connection;
    Class<T> tClass;

    public Session(Connection connection) {
        this.connection = connection;

    }

    //返回一个 dao对应的对象
    //JDK动态代理
    public <T> T getMapper(Class<T> clazz) {
        ClassLoader classLoader = Session.class.getClassLoader();
        MapperProxy proxyInvocation = new MapperProxy(this);
        Object proxy = Proxy.newProxyInstance(classLoader, new Class[]{clazz}, proxyInvocation);
        return (T) proxy;
    }


    //所有都返回list
    public List<T> executeQuery(String sql, Class<T> tClass, Object[] params) throws InstantiationException, IllegalAccessException, InvocationTargetException {
        this.tClass = tClass;
        //存储将来返回出去的所有
        List<T> list = new ArrayList<>();
        //存储每一行从数据库当中查询出来的列明和对应的值
        Map<String, Object> map = new HashMap<String, Object>();
        //每一列都会对应一个实体类对象
        T t = null;
        try {
            PreparedStatement pstmt = connection.prepareStatement(sql);
            if (params != null) {
                for (int i = 0; i < params.length; i++) {
                    pstmt.setObject(i + 1, params[i]);
                }
            }
            ResultSet rs = pstmt.executeQuery();
            ResultSetMetaData md = rs.getMetaData();
            int columns = md.getColumnCount();
            while (rs.next()) {
                if (Integer.class.isAssignableFrom(tClass)) {
                    Integer anInt = rs.getInt(1);
                    list.add((T) anInt);
                } else {
                    for (int i = 1; i <= columns; ++i) {
                        map.put(md.getColumnName(i), rs.getObject(i));
                    }//把一行数据已经封装成为了一个map
                    //先要实例化对象
                    t = tClass.newInstance();
                    //map转实体类对象
                    BeanUtils.copyProperties(t, map);
                    list.add(t);
                }
            }
            return list;
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }


    }



}

3.5接下来开始测试,创建实体对象:

package com.fbatis.entity;

import java.util.List;

public class MemberEntity {

    private Integer id;
    private String account;
    private String password;


    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getAccount() {
        return account;
    }

    public void setAccount(String account) {
        this.account = account;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }
}

 创建接口,注解实现查询的sql语句:

package com.fbatis.test;

import com.fbatis.anno.Select;
import com.fbatis.entity.MemberEntity;

import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

public interface MemperDao {

    @Select("select * from account ")
    public List<MemberEntity> list();


    @Select("select * from account where id= #{id} and password= #{password} ")
    public MemberEntity listOne(Integer id, String password);

    @Select("select count(*) from account ")
    public Integer listCount(Integer id);



}

测试类:

package com.fbatis.test;

import com.fbatis.entity.MemberEntity;
import com.fbatis.session.Session;
import com.fbatis.session.SessionFactory;
import lombok.extern.slf4j.Slf4j;

import java.util.List;
@Slf4j(topic = "e")
public class Test {
    public static void main(String[] args) {
        //log.debug("xxx");
        //1 创建sqlSessionFactory
        //m:配置文件
        SessionFactory factory = new SessionFactory();
        Session session = factory.createSession();
        MemperDao mapper = (MemperDao) session.getMapper(MemperDao.class);
        MemberEntity list = mapper.listOne(2,"300");
        log.debug("id:{}",list.getId());
        log.debug("password:{}",list.getPassword());
        log.debug("account:{}",list.getAccount());
       

    }
}

 

需要注意的是,这只是一个简单的示例,实际的MyBatis框架要比这个复杂得多,还涉及到事务管理、参数映射、结果映射等更多功能和细节。

通过使用动态代理和注解方式,可以实现简洁、灵活的Mapper接口编程模型,提高开发效率。同时,根据实际需求进行优化,如使用连接池管理数据库连接、缓存已解析的SQL语句等,以提升MyBatis框架的性能和可扩展性。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值