0


自定义 手写实现 Mybatis 框架

什么是 MyBatis?

MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。

一、mybatis入门案例

【测试类代码】

public class MybatisTest {
    public static void main(String[] args) throws IOException {
        //1、读取配置文件
        InputStream in = Resources.getResourceAsStream("config/SqlMapConfig.xml");
        //2.创建 SqlSessionFactory 的构建者对象
        SqlSessionFactoryBuilder builder=new SqlSessionFactoryBuilder();
        //3.使用构建者创建工厂对象 SqlSessionFactory
        SqlSessionFactory sessionFactory = builder.build(in);
        //4.使用 SqlSessionFactory 生产 SqlSession 对象
        SqlSession sqlSession = sessionFactory.openSession();
        //5.使用 SqlSession 创建 dao 接口的代理对象
        UserDao userMapper = sqlSession.getMapper(UserDao.class);
        //6.使用代理对象执行查询所有方法 List<User>
        List<User> users = userMapper.findAll();
        for (User user : users) {
            System.out.println(user);
        }
        //7.释放资源
        sqlSession.close();
        in.close();
    }
}

mybatis几个基础API

** 二、API设计与实现**

maven依赖准备

    <dependencies>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.11</version>
        </dependency>
        <!-- 灵活的Java XML框架 -->
        <dependency>
            <groupId>dom4j</groupId>
            <artifactId>dom4j</artifactId>
            <version>1.6.1</version>
        </dependency>
        <!-- Jaxen是用于Java的通用XPath引擎 -->
        <dependency>
            <groupId>jaxen</groupId>
            <artifactId>jaxen</artifactId>
            <version>1.1.6</version>
        </dependency>

        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.12</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

1、Resources类

public class Resources {

    /**
     * 用于加载 .xml文件,获取一个流对象
     *
     * @param xmlPath
     * @return
     */
    public static InputStream getResourceAsStream(String xmlPath) {
        return Resources.class.getClassLoader().getResourceAsStream(xmlPath);
    }
}

2、建造者模式

public interface SqlSessionFactory {

    /**
     * 创建一个新的 SqlSession 对象
     *
     * @return
     */
    SqlSession openSession();
}

public class DefaultSqlSessionFactory implements SqlSessionFactory {

    private InputStream input;

    public void setInput(InputStream input) {
        this.input = input;
    }

    /**
     * 创建一个新的SqlSession对象
     * @return
     */
    @Override
    public SqlSession openSession() {
        DefaultSqlSession sqlSession = new DefaultSqlSession();
        //调用 xml解析器解析.xml文件
        XMLConfigBuilder.loadConfiguration(sqlSession, input);
        return sqlSession;
    }
}

建造者类

public class SqlSessionFactoryBuilder {

    /**
     * 根据传入的流,实现对 SqlSessionFactory 的创建
     *
     * @param inputStream
     * @return
     */
    public SqlSessionFactory build(InputStream inputStream) {
        DefaultSqlSessionFactory factory = new DefaultSqlSessionFactory();
        factory.setInput(inputStream);
        return factory;
    }
}

3、核心配置类Configuration

Configuration类

public class Configuration {

    private String username; //用户名
    private String password;//密码
    private String url;//地址
    private String driver;//驱动

    private Map<String, Mapper> mapperMap; //map 集合 Map<唯一标识,Mapper> 用于保存映射文件中的 sql标识及 sql 语句
    //set、get方法
}

Mapper类

public class Mapper {

    private String queryString;//sql
    private String resultType;//结果类型的全限定类名

    public String getQueryString() {
        return queryString;
    }

    public void setQueryString(String queryString) {
        this.queryString = queryString;
    }

    public String getResultType() {
        return resultType;
    }

    public void setResultType(String resultType) {
        this.resultType = resultType;
    }
}

SqlSession类 (接口将在下一步实现)

public interface SqlSession {

    /**
     * 创建dao接口的代理对象
     *
     * @param daoClass
     * @param <T>
     * @return
     */
    <T> T getMapper(Class<T> daoClass);

    /**
     * 释放资源链接
     */
    void close();
}

** 4、动态代理**

DefaultSqlSession类

public class DefaultSqlSession implements SqlSession {

    private Configuration cfg;

    private Connection con;

    public void setCfg(Configuration cfg) {
        this.cfg = cfg;
    }

    public Connection getConnection() {
        try {
            con = DataSourceUtil.getConnection(cfg);
            return con;
        } catch (Exception e) {
            throw new RuntimeException();
        }
    }

    /**
     * 动态代理
     * 生成需要的Mapper
     *
     * @param daoClass
     * @param <T>
     * @return
     */
    @Override
    public <T> T getMapper(Class<T> daoClass) {
        Connection con = getConnection();
        //动态代理生成对象
        T daoProxy = (T) Proxy.newProxyInstance(daoClass.getClassLoader(),
                                    new Class[]{daoClass} ,
                                    new MapperProxyFactory(con, cfg.getMapperMap()));
        return daoProxy;
    }

    /**
     * 释放资源
     */
    @Override
    public void close() {
        try {
            System.out.println(con);
            con.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    //查询所有方法
    public <E> List<E> selectList(String statement) {
        Mapper mapper = cfg.getMapperMap().get(statement);
        return new Executor().selectList(mapper, con);
    }
}

MapperProxyFactory类

public class MapperProxyFactory implements InvocationHandler {

    private Connection connection;
    private Map<String, Mapper> mappers;

    public MapperProxyFactory(Connection connection, Map<String, Mapper> mappers) {
        this.connection = connection;
        this.mappers = mappers;
    }

    /**
     * Map 中获取 Value(Mapper),使用工具类 Executor 的 selectList 方法
     *
     * @param proxy
     * @param method
     * @param args
     * @return
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        String methodName = method.getName();
        String className = method.getDeclaringClass().getName();
        //拼接key
        String key = className + "." + methodName;
        //,获取对应mapper
        Mapper mapper = mappers.get(key);
        //如果mapper为空
        if (mapper == null) {
            throw new IllegalArgumentException(key + "is not exit");
        }
        Executor executor = new Executor();
        //调用executor的selectList获取所有方法
        return executor.selectList(mapper, connection);
    }
}

**5、Executor执行类 **

public class Executor {

    /**
     * 查询语句执行器,同时解析结果并返回
     *
     * @param mapper
     * @param con
     * @param <E>
     * @return
     */
    public <E> List<E> selectList(Mapper mapper, Connection con) {
        PreparedStatement statement = null;
        ResultSet resultSet = null;
        try {
            //读取mapper数据
            String queryString = mapper.getQueryString(); //获取sql查询语句
            String resultType = mapper.getResultType(); //全限定类名
            Class typeClass = Class.forName(resultType);
            //获取PrepareStatement对象
            statement = con.prepareStatement(queryString);
            //执行sql语句
            resultSet = statement.executeQuery();
            //解析数据
            List<E> list = new ArrayList<>();
            while (resultSet.next()) {
                //通过反射,创建结果实体对象
                E obj = (E) typeClass.newInstance();
                //获取结果集合的元数据
                ResultSetMetaData metaData = resultSet.getMetaData();
                int count = metaData.getColumnCount(); //获取数据列数
                //循环遍历每列数据(下标从1开始)
                for (int i = 1; i <= count; i++) {
                    //获取指定列的名字
                    String name = metaData.getColumnName(i);
                    //根据列名获取对应列的值
                    Object columnValue = resultSet.getObject(name);
                    //java内省机制,通过反射获取javabean的setting方法并通过method执行
                    PropertyDescriptor pd = new PropertyDescriptor(name, typeClass);
                    Method method = pd.getWriteMethod();
                    method.invoke(obj, columnValue);
                }
                //数据添加入list
                list.add(obj);
            }
            //返回解析结果
            return list;
        } catch (Exception e) {
            throw new RuntimeException();
        } finally {
            //释放资源
            release(statement, resultSet);
        }
    }

    /**
     * 释放资源
     *
     * @param ps
     * @param rs
     */
    private void release(PreparedStatement ps, ResultSet rs) {
        if (ps != null) {
            try {
                ps.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }

        if (rs != null) {
            try {
                rs.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}

6、xml解析类

public class XMLConfigBuilder {

    /**
     * 解析主配置文件,把里面的内容填充到 DefaultSqlSession 所需要的地方
     *
     * @param session
     * @param input
     */
    public static void loadConfiguration(DefaultSqlSession session, InputStream input) {
        try {
            //定义封装连接信息的配置对象(mybatis的配置对象)
            Configuration cfg = new Configuration();
            //获取 SAXReader 对象
            SAXReader reader = new SAXReader();
            //根据字节输入流获取 Document 对象
            Document document = reader.read(input);
            //获取根节点
            Element root = document.getRootElement();
            //使用 xpath 中选择指定节点的方式,遍历获取所有 property 节点
            List<Element> propertyElements = root.selectNodes("//property");
            for (Element propertyElement : propertyElements) {
                //判断节点是连接数据库的哪部分信息,取出 name 属性的值
                String name = propertyElement.attributeValue("name");
                if ("driver".equals(name)) {
                    //表示驱动
                    String driver = propertyElement.attributeValue("value");
                    cfg.setDriver(driver);
                }
                if ("url".equals(name)) {
                    //表示连接字符串
                    String url = propertyElement.attributeValue("value");
                    cfg.setUrl(url);
                }
                if ("username".equals(name)) {
                    //表示用户名
                    String username = propertyElement.attributeValue("value");
                    cfg.setUsername(username);
                }
                if ("password".equals(name)) {
                    //表示密码
                    String password = propertyElement.attributeValue("value");
                    cfg.setPassword(password);
                }
            }
            //取出 mappers中的所有 mapper 标签,判断他们使用了 resource 还是 class 属性
            List<Element> mapperElements = root.selectNodes("//mappers/mapper");
            //遍历集合
            for (Element mapperElement : mapperElements) {
                //判断 mapperElement 使用的是哪个属性
                Attribute attribute = mapperElement.attribute("resource");
                if (attribute != null) {
                    //System.out.println("使用的是 XML");
                    String mapperPath = attribute.getValue();
                    //把映射配置文件的内容获取出来,封装成一个 map,给 configuration 中的 mappers 赋值
                    Map<String, Mapper> mappers = loadMapperConfiguration(mapperPath);
                    cfg.setMapperMap(mappers);
                } else {
                    //System.out.println("使用的是注解");
                    String daoClassPath = mapperElement.attributeValue("class");
                    //根据 daoClassPath 获取封装的必要信息,给 configuration 中的 mappers 赋值
                    Map<String, Mapper> mappers = loadMapperAnnotation(daoClassPath);
                    cfg.setMapperMap(mappers);
                }
            }
            //把配置对象传递给 DefaultSqlSession
            session.setCfg(cfg);
        } catch (DocumentException | ClassNotFoundException e) {
            e.printStackTrace();
        } finally {
            try {
                input.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 根据 注释传入的参数,解析 XML,并且封装到 Map 中
     *
     * @param daoClassPath
     * @return
     */
    private static Map<String, Mapper> loadMapperAnnotation(String daoClassPath) throws ClassNotFoundException {
        //定义返回值对象
        Map<String, Mapper> mappers = new HashMap<String, Mapper>();
        //得到 dao 接口的字节码对象
        Class daoClass = Class.forName(daoClassPath);
        //得到 dao 接口中的方法数组
        Method[] methods = daoClass.getMethods();
        //遍历 Method 数组
        for (Method method : methods) {
            //取出每一个方法,判断是否有 select 注解
            if (method.isAnnotationPresent(Select.class)) {
                //创建 Mapper 对象
                Mapper mapper = new Mapper();
                //取出注解的 value 属性值
                Select selectAnno = method.getAnnotation(Select.class);
                String queryString = selectAnno.value();
                mapper.setQueryString(queryString);
                //获取当前方法的返回值,还要求必须带有泛型信息
                Type type = method.getGenericReturnType();//List<User>
                //判断 type 是不是参数化的类型
                if (type instanceof ParameterizedType) {
                    //强转
                    ParameterizedType ptype = (ParameterizedType) type;
                    //得到参数化类型中的实际类型参数
                    Type[] types = ptype.getActualTypeArguments();
                    //取出第一个
                    Class domainClass = (Class) types[0];
                    //获取 domainClass 的类名
                    String resultType = domainClass.getName();
                    //给 Mapper 赋值
                    mapper.setResultType(resultType);
                }
                //组装 key 的信息
                //获取方法的名称
                String methodName = method.getName();
                String className = method.getDeclaringClass().getName();
                String key = className + "." + methodName;
                //给 map 赋值
                mappers.put(key, mapper);
            }
        }
        return mappers;
    }

    /**
     * 根据 xml文件传入的参数,解析 XML,并且封装到 Map 中
     *
     * @param mapperPath
     * @return
     */
    private static Map<String, Mapper> loadMapperConfiguration(String mapperPath) {
        InputStream in = null;
        try {
            //定义返回值对象
            Map<String, Mapper> mappers = new HashMap<String, Mapper>();
            //根据路径获取字节输入流
            in = Resources.getResourceAsStream(mapperPath);
            //根据字节输入流获取 Document 对象
            SAXReader reader = new SAXReader();
            Document document = reader.read(in);
            //获取根节点,获取根节点的 namespace 属性取值
            Element root = document.getRootElement();
            String namespace = root.attributeValue("namespace");//是组成 map 中 key 的部分
            //获取所有的 select 节点
            List<Element> selectElements = root.selectNodes("//select");
            //遍历 select 节点集合
            for (Element selectElement : selectElements) {
                String id = selectElement.attributeValue("id");
                String resultType = selectElement.attributeValue("resultType");
                //取出文本内容 组成 map 中的SQL语句
                String querySql = selectElement.getText();
                //创建 Key
                String key = namespace + "." + id;
                //创建 Value
                Mapper mapper = new Mapper();
                mapper.setQueryString(querySql);
                mapper.setResultType(resultType);
                //把 key 和 value 存入 mappers 中
                mappers.put(key, mapper);
            }
            return mappers;
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            try {
                in.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

** 7、自定义注解类**

以实现注解@select为例子

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

8、工具类DataSourceUtil

public class DataSourceUtil {

    /**
     * 根据 Configuration 配置类信息创建数据库连接
     *
     * @param cfg
     * @return
     */
    public static Connection getConnection(Configuration cfg) {
        try {
            //加载数据库驱动
            Class driver = Class.forName(cfg.getDriver());
            //获取数据库连接
            Connection connection = DriverManager.getConnection(cfg.getUrl(), cfg.getUsername(), cfg.getPassword());
            return connection;
        } catch (ClassNotFoundException | SQLException e) {
            throw new RuntimeException();
        }
    }
}

** 三、测试样例**

** 1、mapper.xml文件映射**

SqlMapConfig.xml文件

<?xml version="1.0" encoding="UTF-8" ?>
<configuration>
    <environments default="mysql">
        <environment id="mysql">
            <!-- 配置事务的类型 -->
            <transactionManager type="JDBC"></transactionManager>
            <!-- 配置连接数据库的信息:用的是数据源(连接池) -->
            <dataSource type="POOLED">
                <property name="driver" value="${driver}"/>
                <property name="url" value="${url}"/>
                <property name="username" value="{username}"/>
                <property name="password" value="{password}"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="UserMapper.xml"/>
    </mappers>
</configuration>

UserMapper.xml文件

<?xml version="1.0" encoding="UTF-8"?>
<mapper namespace="com.qiu.dao.UserDao">
    <!-- 配置查询所有操作 -->
    <select id="findAll" resultType="com.qiu.model.User">
        select *
        from tb_users;
    </select>
</mapper>

这里实体类需要自己设设计以及数据库表

** 注意:** 需要留意的是,由于我们在executor类中,利用java内省机制,获取set方法,给对象属性注入赋值,所以实体的属性名一定要与数据库对应列名完全一样,否则会报错!

【测试类】

public class Test {
    public static void main(String[] args) throws IOException {
        //1.读取配置文件
        InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml");
        //2.创建 SqlSessionFactory 的构建者对象
        SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
        //3.使用构建者创建工厂对象 SqlSessionFactory
        SqlSessionFactory factory = builder.build(in);
        //4.使用 SqlSessionFactory 生产 SqlSession 对象
        SqlSession session = factory.openSession();
        //5.使用 SqlSession 创建 dao 接口的代理对象
        UserDao userDao = session.getMapper(UserDao.class);
        //6.使用代理对象执行查询所有方法 List<User>
        List<User> users = userDao.findAll();
        for (User user : users) {
            System.out.println(user);
        }
        //7.释放资源
        session.close();
        in.close();
    }
}

【测试结果】

** 2、测试注解@select**

配置文件同上、测试类同上

注意将SqlMapConfig.xml文件的mapper标签替换属性值为class,启动使用注解

    <mappers>
        <mapper class="com.qiu.dao.UserDao"/>
    </mappers>

** OK!大工告成!!!**

标签: java mysql mybatis

本文转载自: https://blog.csdn.net/m0_46013789/article/details/125981105
版权归原作者 枫蜜柚子茶 所有, 如有侵权,请联系我们删除。

“自定义 手写实现 Mybatis 框架”的评论:

还没有评论