Mybatis学习,自定义Mybatis的基本实现

Mybatis学习,自定义Mybatis

1.创建maven项目,在pom.xml中导入需要的依赖

  <dependencies>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.22</version>
        </dependency>
        <dependency>
            <groupId>dom4j</groupId>
            <artifactId>dom4j</artifactId>
            <version>1.6.1</version>
        </dependency>
        <dependency>
            <groupId>jaxen</groupId>
            <artifactId>jaxen</artifactId>
            <version>1.1.6</version>
        </dependency>
    </dependencies>

2.写主配置文件SqlMapConfig.xml

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <!--配置环境-->
    <environments default="mysql">
        <!--配置mysql环境-->
        <environment id="mysql">
            <!--配置事务类型-->
            <transactionManager type="JDBC"/>
            <!--配置数据源-->
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url"
                          value="jdbc:mysql://localhost:3306/mybatis?useSSL=true&amp;useUnicode=true&amp;characterEncoding=UTF-8&amp;useJDBCCompliantTimezoneShift=true&amp;serverTimezone=Asia/Shanghai"/>
                <property name="username" value="root"/>
                <property name="password" value="991226abc"/>
            </dataSource>
        </environment>
    </environments>
    <!--指定映射配置文件的位置,即每个dao独立的配置文件-->
    <mappers>
        <mapper resource="com/lzl/dao/UserDao.xml"/>
    </mappers>
</configuration>

3.写UserDao.xml文件

<?xml version="1.0" encoding="UTF-8"?>
<mapper namespace="com.lzl.dao.UserDao">
    <!--配置查询所有,指明封装的类-->
    <select id="findAll" resultType="com.lzl.module.User">
        select * from user
    </select>
</mapper>

4.写Resource类,用于获取配置文件的输入流

/**
 * @Description 本类功能:获取配置文件输入流的方法,通过自身的类加载器获取配置文件的流
 * @Author LZL
 * @Date 2020.10.29-16:00
 */
public class Resources {
    public static InputStream getResourceAsStream(String filePath){
        return Resources.class.getClassLoader().getResourceAsStream(filePath);
    }
}

5.创建SqlSessionFactoryBuilder类,用于创建SqlSessionFactory工厂,我用了一个工具类XMLConfigBuilder解析配置文件得到配置文件对象Configuration,再返回建造的SqlSessionFactory对象

/**
 * @Description 本类功能:SQL session factory的构建者,用于创建sqlsessionfactory工厂
 * @Author LZL
 * @Date 2020.10.29-16:04
 */
public class SqlSessionFactoryBuilder {
    public SqlSessionFactory build(InputStream inputStream){
        Configuration configuration= XMLConfigBuilder.loadConfiguration(inputStream);
        System.out.println("创建工厂");
        return new SqlSessionFactoryImpl(configuration);
    }
}

5.1工具类XMLConfigBuilder

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<Element> 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<Element> 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();//获取属性的值"com/itheima/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<Element> 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();
        }
    }

6.创建SqlSessionFactory接口,其中有创建SqlSession的openSession()方法

public interface SqlSessionFactory {
    SqlSession openSession();
}

6.1创建接口的实现类SqlSessionFactoryImpl,实现open session方法

/**
 * @Description 本类功能:SQL session factory的实现类
 * @Author LZL
 * @Date 2020.10.29-16:32
 */
public class SqlSessionFactoryImpl implements SqlSessionFactory {
    private Configuration cfg;

    public SqlSessionFactoryImpl(Configuration configuration) {
        System.out.println("我是工厂,我被创建了");
        this.cfg = configuration;
    }

    //用于创建一个操作数据库对象
    public SqlSession openSession() {
        return new SqlSessionImpl(cfg);
    }
}

7.创建SqlSession接口,其中有getMapper方法返回代理对象,daoInterfaceClass即为要增强的UserDao接口

/**
 * @Description 本类功能:声明SQL session对象的两个方法,获取mapper对象和关闭流
 * @Author LZL
 * @Date 2020.10.29-16:07
 */
public interface SqlSession {
    <T> T getMapper(Class<T> daoInterfaceClass);
    void close();
}

7.1创建SqlSession接口的实现类,用于返回增强后的代理对象MapperProxy,数据库连接由DataSourceUtil工具类提供

/**
 * @Description 本类功能:SQL session的实现类,可以创建代理对象并返回
 * @Author LZL
 * @Date 2020.10.29-16:36
 */
public class SqlSessionImpl implements SqlSession {
    private Configuration cfg;
private Connection connection;
    public SqlSessionImpl(Configuration cfg) {
        System.out.println("我是SQL session我被创建了");
        this.cfg=cfg;
        connection= DataSourceUtil.getConnection(cfg);
    }

    /**
     * 用于创建代理对象
     * @param daoInterfaceClass
     * @param <T>
     * @return
     */
    public <T> T getMapper(Class<T> daoInterfaceClass) {
        System.out.println("创建代理对象");
              return (T) Proxy.newProxyInstance(daoInterfaceClass.getClassLoader(),new Class[]{daoInterfaceClass},new MapperProxy(cfg.getMappers(),connection));
    }

    /**
     * 释放资源
     */
    public void close() {
        if(connection!=null){
            try {
                connection.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }

    }
}

7.2DataSourceUtil工具类

public class DataSourceUtil {
    public static Connection getConnection(Configuration cfg) {
        try {
            Class.forName(cfg.getDriver());
            Connection connection = DriverManager.getConnection(cfg.getUrl(), cfg.getUsername(), cfg.getPassword());
            if (connection != null) {
                System.out.println("我获取到了数据库连接");
                return connection;
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SQLException e) {
            e.printStackTrace();
        }

        return null;
    }
}

8.创建代理对象类MapperProxy,实现方法findAll,通过传入的mappers,再反射得到要执行方法的全名,com.lzl.com.dao.UserDao.findAll,在mappers中找到对应的Mapper对象,使用Excutor工具类的selectList方法执行sql语句,返回结果

/**
 * @Description 本类功能:UserDao的代理对象,用于增强UserDao的findAll方法,实现findAll方法
 * @Author LZL
 * @Date 2020.10.29-16:40
 */
public class MapperProxy implements InvocationHandler {
    private Map<String ,Mapper> mappers;
    private Connection connection;
    public MapperProxy(Map<String, Mapper> mappers,Connection connection) {
        this.mappers=mappers;
        this.connection=connection;
    }

    /**
     * 用于对方法进行增强,调用findAll
     * @param proxy
     * @param method
     * @param args
     * @return
     * @throws Throwable
     */
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("我是反射增强方法,我被执行了");
        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,connection);
    }
}

8.1Executor工具类的select List方法

 public <E> List<E> selectList(Mapper mapper, Connection conn) {
        System.out.println("我是select list我被执行了");
        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<E> list = new ArrayList<E>();//定义返回值
            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);
        }
    }

9.测试类MybatisTest

public class MyBatisTest {
    public static void main(String[] args) throws IOException {
        //读取配置文件
        InputStream inputStream= Resources.getResourceAsStream("SqlMapConfig.xml");
        //创建SQlsessionFactory工厂
        SqlSessionFactoryBuilder builder=new SqlSessionFactoryBuilder();
        SqlSessionFactory sqlSessionFactory=builder.build(inputStream);
        //使用工厂生产sqlSession对象
        SqlSession sqlSession=sqlSessionFactory.openSession();
        //使用SQlSession创建dao接口的代理对象
        UserDao userDao=sqlSession.getMapper(UserDao.class);
        //使用代理对象执行方法
        List<User> users=userDao.findAll();
        for (User user : users) {
            System.out.println(user.toString());
        }
        //释放资源
        sqlSession.close();
        inputStream.close();
    }
}

程序截图:
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 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、付费专栏及课程。

余额充值