栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

自定义 手写实现 Mybatis 框架

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

自定义 手写实现 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
        List users = userMapper.findAll();
        for (User user : users) {
            System.out.println(user);
        }
        //7.释放资源
        sqlSession.close();
        in.close();
    }
}

mybatis几个基础API

 

 二、API设计与实现

maven依赖准备

    
        
            mysql
            mysql-connector-java
            8.0.11
        
        
        
            dom4j
            dom4j
            1.6.1
        
        
        
            jaxen
            jaxen
            1.1.6
        

        
            log4j
            log4j
            1.2.12
        
        
            junit
            junit
            4.12
            test
        
    

1、Resources类

public class Resources {

    
    public static InputStream getResourceAsStream(String xmlPath) {
        return Resources.class.getClassLoader().getResourceAsStream(xmlPath);
    }
}

2、建造者模式

 

public interface SqlSessionFactory {

    
    SqlSession openSession();
}

public class DefaultSqlSessionFactory implements SqlSessionFactory {

    private InputStream input;

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

    
    @Override
    public SqlSession openSession() {
        DefaultSqlSession sqlSession = new DefaultSqlSession();
        //调用 xml解析器解析.xml文件
        XMLConfigBuilder.loadConfiguration(sqlSession, input);
        return sqlSession;
    }
}

建造者类

public class SqlSessionFactoryBuilder {

    
    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 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 {

    
     T getMapper(Class 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();
        }
    }

    
    @Override
    public  T getMapper(Class 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  List 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 mappers;

    public MapperProxyFactory(Connection connection, Map mappers) {
        this.connection = connection;
        this.mappers = mappers;
    }

    
    @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 {

    
    public  List 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 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);
        }
    }

    
    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 {

    
    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 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 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 mappers = loadMapperConfiguration(mapperPath);
                    cfg.setMapperMap(mappers);
                } else {
                    //System.out.println("使用的是注解");
                    String daoClassPath = mapperElement.attributeValue("class");
                    //根据 daoClassPath 获取封装的必要信息,给 configuration 中的 mappers 赋值
                    Map 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();
            }
        }
    }

    
    private static Map loadMapperAnnotation(String daoClassPath) throws ClassNotFoundException {
        //定义返回值对象
        Map mappers = new HashMap();
        //得到 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
                //判断 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;
    }

    
    private static Map loadMapperConfiguration(String mapperPath) {
        InputStream in = null;
        try {
            //定义返回值对象
            Map mappers = new HashMap();
            //根据路径获取字节输入流
            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 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 {

    
    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文件



    
        
            
            
            
            
                
                
                
                
            
        
    
    
        
    

 UserMapper.xml文件



    
    

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

 

 注意: 需要留意的是,由于我们在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
        List users = userDao.findAll();
        for (User user : users) {
            System.out.println(user);
        }
        //7.释放资源
        session.close();
        in.close();
    }
}

 【测试结果】

 2、测试注解@select

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

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

    
        
    

 OK!大工告成!!!

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/1026833.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号