项目:简易Mybatis

news2024/11/15 11:53:35

目录

一、新建项目

二、新建模块

三、回顾JDBC

四、准备环境

五、使用dom4j解析xml文件

 六、开始,编写Mapper解析API

1、自定义Resources类

2、定义Configuration类

3、定义MappedStatement类

4、定义XmlMapperBuilder类

5、更新一下UserMapper.xml和UserMapper接口

6、测试解析结果

七、解析config.xml文件

1、导入连接池依赖

2、定义XmlConfigBuilder类

3、测试

八、获取SQLSession

1、编写SqlSession接口

2、编写SqlSessionFactory接口

3、编写DefaultSqlSession类实现接口

4、编写DefaultSqlSessionFactory类实现接口

5、编写SqlSessionFactoryBuilder类

6、测试SqlSession是否创建成功

九、编写执行器

1、编写BoundSql类用于封装sql语句

2、编写Executor接口,定义执行器方法

3、编写SimpleExecutor用于实现Executor接口

4、更新DefaultSqlSession类,实现selectList和selectOne方法

5、编写测试类进行测试

十、增加新增接口

(1)更改代码

(2)测试

十一、增加更新接口

(1)更新代码

(2)测试

十二、增加删除接口

(1)修改mapper文件及更改DefaultSqlSession类

(2)测试

十三、优化代码为按标签类型分

(1)bug点

(2)在MappedStatement类中增加sqlType属性

(3)封装时将标签名一同进行封装

(4)修改DefaultSqlSession类中的代码

(5)修改SimpleExecutor类中的方法

(6)测试

十四、优化代码(使框架支持Integer作为参数)

(1)修改SimpleExecutor类的代码

(2)测试

十五、总结


一、新建项目

1、新建空项目

2、命名

3、删除父项目src


二、新建模块

1、右键父项目,新建模块

2、命名


三、回顾JDBC

1、存在问题

  1. 数据库连接创建、释放频繁造成系统资源浪费,从而影响性能。
  2. sql语句存在硬编码,造成代码不易维护。
  3. 使用preparedStatement向占有位符号传参数存在硬编码问题。
  4. 对结果解析存在硬编码(查询列名),sql变化导致解析代码变化。

2、问题解决

  1. 数据库频繁创建连接以及释放资源:连接池
  2. sql语句及参数存在硬编码:配置文件XxxMapper.xml
  3. 手动解析封装返回结果集:反射、内省

1、创建测试类

package test;


import org.junit.Test;

public class TestJdbc {
    @Test
    public void testJdbc(){

    }
}

2、创建测试数据库及表

3、创建实体类

package com.qingti.pojo;

import lombok.Data;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
    private Integer id;
    private String username;
}

4、导入mysql依赖

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.23</version>
        </dependency>

5、编写查询jdbc

package test;


import com.qingti.pojo.User;
import org.junit.Test;

import java.sql.*;
import java.util.ArrayList;
import java.util.List;

public class TestJdbc {
    @Test
    public void testJdbc(){
        Connection connection = null;
        PreparedStatement preparedStatement = null;
        ResultSet resultSet = null;
        List<User> users = new ArrayList<User>();
        try {
            //加载JDBC驱动
            Class.forName("com.mysql.jdbc.Driver");
            //获取连接对象
            connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/handwrite?characterEncoding=utf-8&serverTimezone=Asia/Shanghai", "root", "123456");
            //获取preparedStatement
            preparedStatement = connection.prepareStatement("select * from handwrite.mybatistable");
            //执行sql返回ResultSet
            resultSet = preparedStatement.executeQuery();
            //遍历数据,存入集合
            while (resultSet.next()){
                int id = resultSet.getInt("id");
                String username = resultSet.getString("username");
                User user = new User(id,username);
                users.add(user);
            }
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        } catch (SQLException e) {
            throw new RuntimeException(e);
        } finally {
            //关闭连接
            try {
                if(resultSet!=null){
                    resultSet.close();
                }
                if (preparedStatement!=null){
                    preparedStatement.close();
                }
                if (connection!=null){
                    connection.close();
                }
            } catch (SQLException e) {
                throw new RuntimeException(e);
            } finally {
            }
        }
        System.out.println("查询到的数据是:"+users);
    }
}

6、运行测试

7、编写新增jdbc

    @Test
    public void testInsert(){
        Connection connection = null;
        PreparedStatement preparedStatement = null;
        ResultSet resultSet = null;
        try {
            //加载JDBC驱动
            Class.forName("com.mysql.jdbc.Driver");
            //获取连接对象
            connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/handwrite?characterEncoding=utf-8&serverTimezone=Asia/Shanghai", "root", "123456");
            //获取preparedStatement
            preparedStatement = connection.prepareStatement("INSERT INTO handwrite.mybatistable values (null,?)");
            preparedStatement.setObject(1,"李四");
            //执行sql
            int count = preparedStatement.executeUpdate();
            System.out.println(count>0?"新增成功":"新增失败");
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        } catch (SQLException e) {
            throw new RuntimeException(e);
        } finally {
            //关闭连接
            try {
                if(resultSet!=null){
                    resultSet.close();
                }
                if (preparedStatement!=null){
                    preparedStatement.close();
                }
                if (connection!=null){
                    connection.close();
                }
            } catch (SQLException e) {
                throw new RuntimeException(e);
            } finally {
            }
        }
    }

8、运行测试


四、准备环境

1、编写UserMapper

public interface UserMapper {
    List<User> list();
}

2、编写resource/mapper

<mapper namespace="com.qingti.mapper.UserMapper">
<!--    查询-->
    <select id="list" resultType="com.qingti.pojo.User">
        select * from mybatistable
    </select>
</mapper>

3、编写mybatis-config

<?xml version="1.0" encoding="UTF-8" ?>
<configuration>
<!--数据库配置信息-->
    <dataSource>
        <!--数据库的驱动地址-->
        <property name="driverClass" value="com.mysql.cj.jdbc.Driver"/>
        <!--连接字符串-->
        <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/handwrite?useUnicode=true&amp;characterEncoding=utf-8&amp;serverTimezone=Asia/Shanghai"/>
        <property name="username" value="root"/>
        <property name="password" value="123456"/>
    </dataSource>
    <!--存放mapper.xml的全路径-->
    <mapper resource="mapper/UserMapper.xml"/>
</configuration>

4、编写测试代码

public class TestMybatis {
    UserMapper userMapper;

    @Before
    public void init(){
        //解析xml
        String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        SqlSession sqlSession = sqlSessionFactory.openSession();
        //框架底层使用JDK动态代理给接口生成实现类对象
        userMapper = sqlSession.getMapper(UserDao.class);
    }

    @Test
    public void testList(){
        List<User> list = userMapper.list();
        for (User user : list) {
            System.out.println(user);
        }
    }
}

五、使用dom4j解析xml文件

1、导入dom4j依赖

        <dependency>
            <groupId>dom4j</groupId>
            <artifactId>dom4j</artifactId>
            <version>1.1</version>
        </dependency>

2、在test目录创建测试xml

<?xml version="1.0" encoding="UTF-8" ?>
<books>
    <book id="1">
        <name>1</name>
        <id>1</id>
    </book>
    <book id="2">
        <name>2</name>
        <id>2</id>
    </book>
    <book id="3">
        <name>3</name>
        <id>3</id>
    </book>
</books>

3、创建测试类

package test;

import org.dom4j.Attribute;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.junit.Test;

import java.util.List;

public class TestBook {
    @Test
    public void test(){
        try {
            //创建一个解析器
            SAXReader saxReader = new SAXReader();
            //获取一个文档对象
            Document document = saxReader.read("D:\\selfstudy\\MybatisHandWrite\\hand-write-ssm\\hand-write-mybatis\\src\\test\\resources\\book.xml");
            //获取xml文件的根节点
            Element rootElement = document.getRootElement();
            System.out.println("根节点的名字是"+rootElement.getName());
            //获取子节点的集合
            List<Element> elements = rootElement.elements();
            System.out.println("子节点的个数为:"+elements.size());
            for (Element element : elements) {
                Attribute id = element.attribute("id");
                String value = id.getValue();
                System.out.println("id的值为:"+value);
                //book节点下的子节点集合
                List<Element> elements1 = element.elements();
                for (Element element1 : elements1) {
                    //标签名
                    String tagName = element1.getName();
                    //标签内的内容
                    String text = element1.getText();
                    System.out.println(tagName+"="+text);
                }
                System.out.println("------------------------------");
            }
        } catch (DocumentException e) {
            throw new RuntimeException(e);
        } finally {
        }

    }
}

4、运行测试


 六、开始,编写Mapper解析API

1、自定义Resources类

Resources类的作用是获取一个类加载器,根据配置文件的路径,将配置文件加载成字节输入流存储在内存中

创建Resources类

@Data
public class Resources {

    /**
     * 根据路径将配置文件加载为字节流的形式,存储在内存中
     * @param path 配置文件的位置
     * @return 返回的字节流
     */
    public static InputStream getResourceAsStream(String path) {
        //加载类路径下的配置文件,以字节流的形式返回 
        InputStream resourceAsStream = Resources.class.getClassLoader().getResourceAsStream(path);
        return resourceAsStream;
    }
}

2、定义Configuration类

对sql语句进行封装

/**
 * 对sql语句进行封装
 */
@Data
public class Configuration {

    /**
     * 数据源
     */
    private DataSource dataSource;

    /**
     * 封装的mapper.xml文件中的sql语句,因为mapper中不止一条sql语句
     */
    Map<String,MappedStatement> mappedStatementMap = new ConcurrentHashMap<>();
}   

3、定义MappedStatement类

MappedStatement类作用是封装UserMapper.xml文件解析之后的SQL语句信息,在底层框架可以使用Dom4j进行解析

/**
 * 对sql语句进行解析
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class MappedStatement {
    //id标识
    private String id;
    //sql语句返回值
    private String resultType;
    //参数类型
    private String parameterType;
    //sql语句
    private String sql;
}

4、定义XmlMapperBuilder类

使用dom4j解析Mapper.xml配置文件

/**
 * 使用dom4j解析Mapper.xml配置文件
 */
public class XmlMapperBuilder {
    /**
     * 配置数据封装对象
     */
    private Configuration configuration;

    public XmlMapperBuilder(Configuration configuration) {
        this.configuration = configuration;
    }

    /**
     *  传入配置文件的字节流,解析配置文件,得到配置文件的封装
     * @param intputStream
     */
    public void parse(InputStream intputStream) throws DocumentException {
        SAXReader saxReader = new SAXReader();
        //获取文档对象
        Document document = saxReader.read(intputStream);
        //获取根节点
        Element rootElement = document.getRootElement();
        //获取根节点的属性
        Attribute namespace = rootElement.attribute("namespace");
        //com.qingti.pojo.User
        String namespaceValue = namespace.getValue();
        //xpath解析,解析xml配置文件,获取所有查询相关的节点
        List selectNodes = rootElement.selectNodes("//select");
        //xpath解析,解析xml配置文件,获取所有增加相关的节点
        List insertNodes = rootElement.selectNodes("//insert");
        //xpath解析,解析xml配置文件,获取所有修改相关的节点
        List updateNodes = rootElement.selectNodes("//update");
        //xpath解析,解析xml配置文件,获取所有删除相关的节点
        List deleteNodes = rootElement.selectNodes("//delete");

        List<Element> allNodes = new ArrayList<>();
        allNodes.addAll(selectNodes);
        allNodes.addAll(insertNodes);
        allNodes.addAll(updateNodes);
        allNodes.addAll(deleteNodes);

        for (Element element : allNodes) {
            //获取每条sql的id值
            String id = element.attributeValue("id");
            //获取返回值
            String resultType = element.attributeValue("resultType");
            //获取参数类型
            String parameterType = element.attributeValue("parameterType");
            //获取每个mappr节点中的sql语句
            String sqlText = element.getTextTrim();
            //封装对象
            MappedStatement mappedStatement = new MappedStatement();
            mappedStatement.setId(id);
            mappedStatement.setResultType(resultType);
            mappedStatement.setParameterType(parameterType);
            mappedStatement.setSql(sqlText);

            String key = namespaceValue+"."+id;
            configuration.getMappedStatementMap().put(key,mappedStatement);

        }

    }

}

5、更新一下UserMapper.xml和UserMapper接口

(1)更新xml中的sql语句

<mapper namespace="com.qingti.mapper.UserMapper">
<!--    查询-->
    <select id="list" resultType="com.qingti.pojo.User">
        select * from mybatistable
    </select>
<!--    id查-->
    <select id="findById" resultType="com.qingti.pojo.User" parameterType="java.lang.Integer">
        select * from mybatistable where id=#{id}
    </select>
<!--    新增-->
    <insert id="insert" resultType="java.lang.Integer" parameterType="com.qingti.pojo.User">
        insert into mybatistable values (null,#{username})
    </insert>
<!--    修改-->
    <update id="update" resultType="java.lang.Integer" parameterType="com.qingti.pojo.User">
        update mybatistable set username=#{username} where id=#{id}
    </update>
<!--    删除-->
    <delete id="delete" resultType="java.lang.Integer" parameterType="java.lang.Integer">
        delete from mybatistable where id=#{id}
    </delete>
</mapper>

(2)更新方法接口

public interface UserMapper {
    List<User> list();

    User findById(Integer id);

    Integer add(User user);

    Integer update(User user);

    Integer delete(Integer id);
}

6、测试解析结果

public class TestMapper {
    @Test
    public void test(){

        try {
            Configuration configuration = new Configuration();
            InputStream resourceAsStream = Resources.getResourceAsStream("mapper/UserMapper.xml");
            XmlMapperBuilder mapperBuilder = new XmlMapperBuilder(configuration);
            mapperBuilder.parse(resourceAsStream);
            System.out.println(configuration);
        } catch (DocumentException e) {
            throw new RuntimeException(e);
        }
    }
}


七、解析config.xml文件

1、导入连接池依赖

       <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.16</version>
        </dependency>

2、定义XmlConfigBuilder类

public class XmlConfigBuilder {
    private Configuration configuration;

    public XmlConfigBuilder(Configuration configuration) {
        this.configuration = configuration;
    }

    public Configuration parseMyBatisXConfig(InputStream inputStream) throws DocumentException {
        SAXReader saxReader = new SAXReader();
        Document document = saxReader.read(inputStream);
        //根节点 configuration
        Element rootElement = document.getRootElement();
        List<Element> property = rootElement.selectNodes("//property");
        Properties properties = new Properties();
        for (Element element : property) {
            String name = element.attributeValue("name");
            String value = element.attributeValue("value");
            properties.setProperty(name,value);
        }
        //初始化数据库连接池
        DruidDataSource druidDataSource = new DruidDataSource();
        druidDataSource.setDriverClassName(properties.getProperty("driverClass"));
        druidDataSource.setUrl(properties.getProperty("jdbcUrl"));
        druidDataSource.setUsername(properties.getProperty("username"));
        druidDataSource.setPassword(properties.getProperty("password"));
        //设置数据库数据源
        configuration.setDataSource(druidDataSource);
        //Mybatis的核心配置文件,映射Mapper.xml文件
        List<Element> list = rootElement.selectNodes("//mapper");
        for (Element element : list) {
            String resource = element.attributeValue("resource");
            InputStream resourceAsStream = Resources.getResourceAsStream(resource);
            XmlMapperBuilder xmlMapperBuilder = new XmlMapperBuilder(configuration);
            xmlMapperBuilder.parse(resourceAsStream);
        }
        return configuration;
    }
}

3、测试

public class TestXml {
    @Test
    public void test() throws DocumentException {
        Configuration configuration = new Configuration();
        XmlConfigBuilder xmlConfigBuilder = new XmlConfigBuilder(configuration);
        InputStream resourceAsStream = Resources.getResourceAsStream("mybatis-config.xml");
        xmlConfigBuilder.parseMyBatisXConfig(resourceAsStream);
        System.out.println("解析完毕!");
        System.out.println(configuration);

    }
}

现在我们可以得到xml文件中的数据了,于是我们完成了第一步,加载配置文件


八、获取SQLSession

1、编写SqlSession接口

SqlSession是提供与数据库交互的各种方法的接口

public interface SqlSession {
    /**
     *查询所有数据
     * @param statementId sql语句唯一ID
     * @param params 查询sql语句所需参数,可变参数
     * @param <T>
     */
    <T> List<T> selectList(String statementId, Object... params)throws Exception;

    /**
     *按条件查询单个对象
     * @param statementId sql语句唯一ID
     * @param params 查询sql语句所需参数,可变参数
     * @param <T>
     */
    <T> T selectOne(String statementId, Object... params)throws Exception;

    /**
     *新增
     * @param statementId sql语句唯一ID
     * @param params 查询sql语句所需参数,可变参数
     * @param <T>
     */
    <T> T insert(String statementId, Object... params)throws Exception;

    /**
     *更新
     * @param statementId sql语句唯一ID
     * @param params 查询sql语句所需参数,可变参数
     * @param <T>
     */
    <T> T update(String statementId, Object... params)throws Exception;

    /**
     *删除
     * @param statementId sql语句唯一ID
     * @param params 查询sql语句所需参数,可变参数
     * @param <T>
     */
    <T> T delete(String statementId, Object... params)throws Exception;

    /**
     * 为Mapper层的接口JDK动态代理生成实现类
     * @param mapperClass 字节码
     * @return 接口的代理类对象
     * @param <T> 反向
     * @throws Exception
     */
    <T> T getMapper(Class<?> mapperClass)throws Exception;
}

2、编写SqlSessionFactory接口

SqlSessionFactory是MyBatis的关键对象,它是单个数据库映射关系经过编译后的内存镜像

public interface SqlSessionFactory {
    //获取sqlSession
    SqlSession openSession();
}

3、编写DefaultSqlSession类实现接口

(1)先创建一个Command类来区分sql语句

public enum CommandType {
    INSERT,
    UPDATE,
    DELETE
}

(2)编写实现类

public class DefaultSqlSession implements SqlSession{

    //封装的配置信息
    private Configuration configuration;

    public DefaultSqlSession(Configuration configuration) {
        this.configuration = configuration;
    }

    @Override
    public <T> List<T> selectList(String statementId, Object... params) throws Exception {
        return null;
    }

    @Override
    public <T> T selectOne(String statementId, Object... params) throws Exception {
        return null;
    }

    @Override
    public <T> T insert(String statementId, Object... params) throws Exception {
        return null;
    }

    @Override
    public <T> T update(String statementId, Object... params) throws Exception {
        return null;
    }

    @Override
    public <T> T delete(String statementId, Object... params) throws Exception {
        return null;
    }

    /**
     * 为Mapper层的接口JDK动态代理生成实现类
     * @param mapperClass 字节码
     * @return 接口的代理类对象
     * @param <T> 反向
     * @throws Exception
     */
    @Override
    public <T> T getMapper(Class<?> mapperClass) throws Exception {
        Object instance = Proxy.newProxyInstance(mapperClass.getClassLoader(), new Class[]{mapperClass}, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                //接口中的方法名
                String methodName = method.getName();
                //接口的全类名
                String className = method.getDeclaringClass().getName();
                //拼接Sql的唯一标识
                String statementId = className + "." + methodName;
                //获取方法被调用后的返回值类型
                Type genericReturnType = method.getGenericReturnType();
                if(methodName.contains(CommandType.INSERT.toString())){
                    return insert(statementId,args);
                } else if (methodName.contains(CommandType.DELETE.toString())) {
                    return delete(statementId,args);
                } else if (methodName.contains(CommandType.UPDATE.toString())) {
                    return update(statementId,args);
                }
                //判断是否进行了泛型类型的参数化(判断返回值类型是否是泛型)
                if (genericReturnType instanceof ParameterizedType){
                    List<Object> objects = selectList(statementId, args);
                    return objects;
                }else{
                    return selectOne(statementId, args);
                }

            }
        });
        return (T)instance;
    }
}

4、编写DefaultSqlSessionFactory类实现接口

public class DefaultSqlSessionFactory implements SqlSessionFactory{

    private Configuration configuration;

    public DefaultSqlSessionFactory(Configuration configuration){
        this.configuration = configuration;
    }

    @Override
    public SqlSession openSession() {
        return new DefaultSqlSession(configuration);
    }
}

5、编写SqlSessionFactoryBuilder类

该类用于生成SqlSessionFactory对象

public class SqlSessionFactoryBuilder {
    public SqlSessionFactory build(InputStream inputStream) throws DocumentException {
        //获取configuration对象
        Configuration configuration = new Configuration();
        XmlConfigBuilder xmlConfigBuilder = new XmlConfigBuilder(configuration);
        xmlConfigBuilder.parseMyBatisXConfig(inputStream);
        //创建SqlSessionFactory
        DefaultSqlSessionFactory sqlSessionFactory = new DefaultSqlSessionFactory(configuration);
        return sqlSessionFactory;

    }
}

6、测试SqlSession是否创建成功

找到TestMysbatis并进行测试


九、编写执行器

1、编写BoundSql类用于封装sql语句

@Data
@NoArgsConstructor
@AllArgsConstructor
public class BoundSql {
    //要执行的sql语句
    private String sqlText;
    //执行sql的参数集合
    private List<String> parameterMappingList = new ArrayList<>();

}

2、编写Executor接口,定义执行器方法

/**
 * sql语句执行器
 */
public interface Executor {
    <T>List<T> query(Configuration configuration, MappedStatement mappedStatement,Object... params) throws SQLException, ClassNotFoundException, Exception;
}

3、编写SimpleExecutor用于实现Executor接口

主要包含:

  • Sql语句的转换
  • Sql的执行
  • 返回值的封装
/**
 * sql语句的执行器
 */
public class SimpleExecutor implements Executor{

    @Override
    public <T> List<T> query(Configuration configuration, MappedStatement mappedStatement, Object... params) throws Exception {
        //1、获取数据库连接
        Connection connection = configuration.getDataSource().getConnection();
        //2、获取要执行的sql语句
        String sql = mappedStatement.getSql();//拿到配置文件中的原始sql语句
        //转换sql语句,把#{}转换为?
        BoundSql boundSql = this.getBoundSql(sql);
        //获取
        PreparedStatement preparedStatement = connection.prepareStatement(boundSql.getSqlText());
        //设置参数:com.qingti.pojo.User
        String parameterType = mappedStatement.getParameterType();
        Class<?> classType = this.getClassType(parameterType);
        //获取sql语句参数集合
        List<String> parameterMappingList = boundSql.getParameterMappingList();
        for (int i = 0;i < parameterMappingList.size();i++) {
            String content = parameterMappingList.get(i);
            //反射
            Field declaredField = classType.getDeclaredField(content);
            declaredField.setAccessible(true);
            //取出参数
            Object data = declaredField.get(params[0]);
            preparedStatement.setObject(i+1,data);
        }
        //执行SqL
        String id = mappedStatement.getId();
        ResultSet resultSet = null;
        if(id.contains(CommandType.DELETE.toString())||id.contains(CommandType.INSERT.toString())||id.contains(CommandType.UPDATE.toString())){
            //增删改
            Integer result = preparedStatement.executeUpdate();
            ArrayList<Integer> resultList = new ArrayList<>();
            resultList.add(result);
            return (List<T>)resultList;
        }else {
            //查询
            resultSet = preparedStatement.executeQuery();
        }
        //获取返回值的类型
        String resultType = mappedStatement.getResultType();
        Class<?> returnTypeClass = this.getClassType(resultType);
        List<Object> objects = new ArrayList<>();
        while (resultSet.next()){
            //调无参构造方法生成对象
            Object instance = returnTypeClass.newInstance();
            ResultSetMetaData metaData = resultSet.getMetaData();
            for (int i = 1; i <= metaData.getColumnCount(); i++) {
                //字段名字
                String columnName = metaData.getColumnName(i);
                //获取值
                Object value = resultSet.getObject(columnName);
                //属性封装
                //使用反射根据数据库表和实体类的属性和字段对应关系数据封装
                PropertyDescriptor propertyDescriptor = new PropertyDescriptor(columnName,returnTypeClass);
                Method writeMethod = propertyDescriptor.getWriteMethod();
                writeMethod.invoke(instance,value);
            }
            objects.add(instance);
        }
        return (List<T>)objects;
    }

    Map<Integer,Integer> map = new TreeMap<Integer,Integer>();
    int findPosition = 0;
    List<String> parameterMappings = new ArrayList<>();
    /**
     * 根据类的全名称获取Class
     * @param parameterType
     * @return
     * @throws ClassNotFoundException
     */
    public Class<?> getClassType(String parameterType) throws ClassNotFoundException {
        if(parameterType!=null){
            Class<?> aClass = Class.forName(parameterType);
            return aClass;
        }
        return null;
    }



    /**
     * 转换API
     * 1、将#{}使用?代替
     * 2、解析出#{}内的值进行存储
     * @param sql
     * @return
     */
    private BoundSql getBoundSql(String sql){
        //完成sql语句解析工作
        this.parserSql(sql);
        Set<Map.Entry<Integer,Integer>> entries = map.entrySet();
        for (Map.Entry<Integer, Integer> entry : entries) {
            Integer key = entry.getKey()+2;
            Integer value = entry.getValue();
            parameterMappings.add(sql.substring(key,value));
        }
        for (String s : parameterMappings) {
            sql = sql.replace("#{"+s+"}","?");
        }
        BoundSql boundSql = new BoundSql(sql, parameterMappings);
        return boundSql;
    }

    private void parserSql(String sql){
        int openIndex = sql.indexOf("#{",findPosition);
        if (openIndex != -1){
            int endIndex = sql.indexOf("}",findPosition+1);
            if(endIndex != -1){
                map.put(openIndex,endIndex);
                findPosition = endIndex+1;
                parserSql(sql);//递归检查#{}
            }else{
                System.out.println("SQL语句中参数错误..");
            }
        }
    }

}

4、更新DefaultSqlSession类,实现selectList和selectOne方法

使用Executor对象完成数据库的查询

    @Override
    public <T> List<T> selectList(String statementId, Object... params) throws Exception {
        //将SimpleExecutorQuery方法完成查询
        SimpleExecutor simpleExecutor = new SimpleExecutor();
        MappedStatement mappedStatement = this.configuration.getMappedStatementMap().get(statementId);
        List<Object> list = simpleExecutor.query(configuration, mappedStatement, params);
        return (List<T>)list;
    }

    @Override
    public <T> T selectOne(String statementId, Object... params) throws Exception {
        List<Object> objects = this.selectList(statementId, params);
        if ((objects.size()==1)){
            return (T) objects.get(0);
        }else if(objects.size()>1){
            throw new RuntimeException("查询结果为空或者查询结果不唯一!");
        }else{
            throw new RuntimeException("查询结果为空!");
        }
    }

5、编写测试类进行测试

public class TestMybatis {
    UserMapper userMapper;

    @Before
    public void init(){
        try {
            //解析xml
            String resource = "mybatis-config.xml";
            //加载配置文件,并得到字节流
            InputStream inputStream = Resources.getResourceAsStream(resource);
            //创建线程工厂
            SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
            SqlSession sqlSession = sqlSessionFactory.openSession();
            System.out.println(sqlSession);
            //框架底层使用JDK动态代理给接口生成实现类对象
            userMapper = sqlSession.getMapper(UserMapper.class);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    @Test
    public void testList(){

        List<User> list = userMapper.list();
        for (User user : list) {
            System.out.println(user);
        }
    }

    @Test
    public void testFindById(){
        User user = userMapper.findById(1);
        System.out.println(user);
    }
}

(1)执行后发现报错

(2)debug进行错误定位

(3)发现是因为程序想在Integer类中找到id属性时出错,于是更改findById的传入值为User

(4)重新测试

    @Test
    public void testFindById(){
        User u = new User();
        u.setId(1);
        User user = userMapper.findById(u);
        System.out.println(user);
    }


十、增加新增接口

(1)更改代码

    @Override
    public <T> T insert(String statementId, Object... params) throws Exception {
        SimpleExecutor simpleExecutor = new SimpleExecutor();
        MappedStatement mappedStatement = this.configuration.getMappedStatementMap().get(statementId);
        List<Object> list = simpleExecutor.query(configuration, mappedStatement, params);
        if (list.size()>0){
            return (T) list.get(0);
        }else {
            return (T) "0";
        }
    }

(2)测试

    @Test
    public void testInsert(){
        User user = new User();
        user.setUsername("qingti");
        int insert = userMapper.insert(user);
        System.out.println(insert>0?"新增成功":"新增失败");
    }


十一、增加更新接口

(1)更新代码

    @Override
    public <T> T update(String statementId, Object... params) throws Exception {
        SimpleExecutor simpleExecutor = new SimpleExecutor();
        MappedStatement mappedStatement = this.configuration.getMappedStatementMap().get(statementId);
        List<Object> list = simpleExecutor.query(configuration, mappedStatement, params);
        if (list.size()>0){
            return (T) list.get(0);
        }else {
            return (T) "0";
        }
    }

(2)测试

    @Test
    public void testUpdate(){
        User user = new User(6,"kunkun");
        int update = userMapper.update(user);
        System.out.println(update>0?"修改成功":"修改失败");

    }


十二、增加删除接口

(1)修改mapper文件及更改DefaultSqlSession类

    @Override
    public <T> T delete(String statementId, Object... params) throws Exception {
        SimpleExecutor simpleExecutor = new SimpleExecutor();
        MappedStatement mappedStatement = this.configuration.getMappedStatementMap().get(statementId);
        List<Object> list = simpleExecutor.query(configuration, mappedStatement, params);
        if (list.size()>0){
            return (T) list.get(0);
        }else {
            return (T) "0";
        }
    }

(2)测试

    @Test
    public void testDelete(){
        User user = new User();
        user.setId(6);
        int delete = userMapper.delete(user);
        System.out.println(delete>0?"删除成功":"删除失败");

    }


十三、优化代码为按标签类型分

(1)bug点

在DefaultSqlSession类的getMapper方法中

这种写法只是简单按照mapper中的方法名来判断用的是哪种sql语句(只能识别insert、update、delete、select)

(2)在MappedStatement类中增加sqlType属性

(3)封装时将标签名一同进行封装

(4)修改DefaultSqlSession类中的代码

    @Override
    public <T> T getMapper(Class<?> mapperClass) throws Exception {
        Object instance = Proxy.newProxyInstance(mapperClass.getClassLoader(), new Class[]{mapperClass}, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                //接口中的方法名
                String methodName = method.getName();
                //接口的全类名
                String className = method.getDeclaringClass().getName();
                //拼接Sql的唯一标识
                String statementId = className + "." + methodName;
                //获取方法被调用后的返回值类型
                Type genericReturnType = method.getGenericReturnType();

                Map<String, MappedStatement> mappedStatementMap = configuration.getMappedStatementMap();
                MappedStatement mappedStatement = mappedStatementMap.get(statementId);

                if("insert".equals(mappedStatement.getSqlType())){
                    return insert(statementId,args);
                } else if ("delete".equals(mappedStatement.getSqlType())) {
                    return delete(statementId,args);
                } else if ("update".equals(mappedStatement.getSqlType())) {
                    return update(statementId,args);
                }
                //判断是否进行了泛型类型的参数化(判断返回值类型是否是泛型)
                if (genericReturnType instanceof ParameterizedType){
                    List<Object> objects = selectList(statementId, args);
                    return objects;
                }else{
                    return selectOne(statementId, args);
                }

            }
        });
        return (T)instance;
    }

(5)修改SimpleExecutor类中的方法

(6)测试

此时,我们将insert的方法名改为add

修改测试方法为add


十四、优化代码(使框架支持Integer作为参数)

(1)修改SimpleExecutor类的代码

        for (int i = 0;i < parameterMappingList.size();i++) {
            String content = parameterMappingList.get(i);
            //若不是Integer,则进行反射;否则直接传入参数
            if (!"java.lang.Integer".equals(parameterType)){
                //反射
                Field declaredField = classType.getDeclaredField(content);
                declaredField.setAccessible(true);
                //取出参数
                Object data = declaredField.get(params[0]);
                preparedStatement.setObject(i+1,data);
            }else {
                preparedStatement.setObject(i+1,params[0]);
            }

        }

(2)测试


十五、总结

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1907066.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

一文彻底带你搞懂什么是适配器模式!!

一文彻底带你搞懂什么是适配器模式&#xff01;&#xff01; 什么是适配器模式&#xff1f;适配器的两种实现方式适用情况代码示例背景类适配器对象适配器 IO流中的实际应用应用扩展 总结 什么是适配器模式&#xff1f; 适配器模式&#xff08;Adapter Pattern&#xff09;是作…

web自动化(六)unittest 四大组件实战(京东登录搜索加入购物车)

Unittest框架 Unittest框架:框架测试模块测试管理模块测试统计模块&#xff0c;python的内置模块 import unittest Unittest框架四大组件: 1、TestCase 测试用例 2.TestFixture 测试用例夹具 测试用例需要执行的前置和后置 3.TestSuite 测试套件 把需要执行的测试用例汇总在一…

什么是企业服务总线?它包含哪些技术组件?

我们每个人都会去医院&#xff0c;您描述下我们去医院的场景&#xff0c;然后引出这个挂号流程&#xff0c;通过挂号流程中的一个问题或者什么东西来吸引他的好奇心&#xff0c;这样呢&#xff1f;会比现在的预设场景好一些。我举个例子&#xff0c;人工智能怎么帮人看病。如果…

前端面试题23(css3)

关于CSS3的面试题&#xff0c;我们可以从多个维度来探讨&#xff0c;包括但不限于选择器、盒模型、布局技术、动画与过渡、响应式设计等。下面我会列举一些典型的CSS3面试问题&#xff0c;并尽可能提供详细的解答或示例代码。 1. CSS3中新增了哪些选择器&#xff1f; 答案: C…

【Java安装】windows10+JDK21+IDEA

文章目录 一、JDK安装1. 下载完成后按照自己需要的位置安装2. 配置环境变量2.1 JAVA_HOME变量2.2 PATH配置 3. 验证4. helloworld 二、IDEA安装三、IDEA-HelloWorld 一、JDK安装 JDK安装链接 1. 下载完成后按照自己需要的位置安装 2. 配置环境变量 2.1 JAVA_HOME变量 安装…

手机飞行模式是什么意思?3个方法教你如何开启

在现代生活中&#xff0c;手机已经成为我们日常生活中不可或缺的一部分。然而&#xff0c;有时我们需要暂时切断手机的通信功能&#xff0c;比如在飞机上、开会时或需要安静休息的时候。这时候&#xff0c;苹果手机上的“飞行模式”功能就派上了用场。 那么&#xff0c;手机飞…

【从零到一,如何搭建本地AI大模型】

摘要: 本文主要记录这一段时间对本地大模型搭建的心得。 作为一个资深程序员,在AI席卷全球的时候,深深感觉到了一丝危机感,不禁有一个想法不断在脑海闪现:我会不会真的哪一天被AI给取代了? 从哪入手 程序员出生的我,掌握了很多语言,从前端到数据库,再到运维,基本都…

uniapp-小程序获取用户位置

1. 需要在微信公众平台进行接口的申请。选择自己需要用的接口。 2. 在app.json文件中配置permission和requiredPrivateInfos。requiredPrivateInfos里面是你需要使用的接口。 3. 配置完成后&#xff0c;就可以使用了。 相关获取位置API的链接 4. 如果要获取当前位置到某一个指…

VS 附加进程调试

背景&#xff1a; 此方式适合VS、代码和待调试的exe在同一台机器上。 一、还原代码到和正在跑的exe同版本 此操作可以保证能够调试生产环境的exe 二、设置符号路径 1.调试->选项 三、附加进程 方式1&#xff1a; 打开VS&#xff0c;调试->附加到进程&#xff0c;出…

【MySQL】MySQL连接池原理与简易网站数据流动是如何进行

MySQL连接池原理与简易网站数据流动是如何进行 1.MySQL连接池原理2.简易网站数据流动是如何进行 点赞&#x1f44d;&#x1f44d;收藏&#x1f31f;&#x1f31f;关注&#x1f496;&#x1f496; 你的支持是对我最大的鼓励&#xff0c;我们一起努力吧!&#x1f603;&#x1f60…

【Go】常见的变量与常量

变量 常见的变量声明方式 一、声明单个变量的多种方式 1.声明一个变量初始化一个值 //声明变量 默认值是0&#xff0c;var a int//初始化一个值a 1fmt.Println(a) 2. 在初始化的时候省去数据类型&#xff0c;通过值自动匹配当前的变量的数据类型 var b 2fmt.Println(&quo…

7月6日 VueConf 技术大会即将在深圳举办

7月6日&#xff0c;VueConf 2024 即将在深圳召开&#xff0c;本次大会正值 Vue.js 十周年&#xff0c;旨在聚焦 Vue.js 社区的成员&#xff0c;分享最新的技术动态、经验以及创新实践。 本次参与 VueConf 大会的是来自全球 Vue.js 核心团队成员、行业专家及前端开发者。其中&a…

Java语言程序设计——篇二(1)

Java语言基础 数据类型关键字与标识符关键字标识符 常量与变量1、常量2、变量 类型转换自动类型转换强制类型转换 数据类型 数据的基本要素数据的性质&#xff08;数据结构&#xff09;数据的取值范围&#xff08;字节大小&#xff09;数据的存储方式参与的运算 Java是一门强类…

NodeJS蔬菜自产零售混合销售平台-计算机毕业设计源码10149

摘 要 随着移动互联网的快速发展&#xff0c;购物方式也发生了巨大的变化。蔬菜作为消费者生活中必不可少的商品之一&#xff0c;在移动互联网时代也迎来了新的购物方式——购物小程序。购物小程序是一种基于手机应用平台的轻量级应用程序&#xff0c;用户可以通过它方便地浏览…

C++第二弹 -- C++基础语法下(引用 内联函数 auto关键字 范围for 指针空值)

本篇大纲 前言一. 引用续讲1. 传值,传引用效率对比2. 类型转换和表达式传引用的注意事项3. 引用与指针 二. 内联函数1. 概念2. 特性3. 面试题 三. auto关键字(C11)1. 类型别名思考2. auto简介3. auto的使用细则4. auto不能推导的场景 四. 基于范围的for循环(C11)1. 范围for的语…

3DMAX软件如何导出和导入模型

在3DMAX软件中导出和导入模型的过程相对直观&#xff0c;以下是具体的步骤&#xff1a;导出模型&#xff1a;1、选择模型&#xff1a;首先&#xff0c;在3DMAX的视图中选择你想要导出的模型。2、导出设置&#xff1a;点击菜单栏中的“文件”&#xff08;File&#xff09;&#…

现货黄金技术出现这一信号赶紧止损!

很多现货黄金投资者都并不知道&#xff0c;移动平均线除了可以用于寻找进场的机会&#xff0c;还可以用来设置止损&#xff0c;让自己在交易中更好地进行防守。其实移动平均线止损&#xff0c;是常用的技术止损方法之一&#xff0c;本文将和大家分享怎样利用均线设置止损点&…

鸿蒙开发:Universal Keystore Kit(密钥管理服务)【加密导入密钥(C/C++)】

加密导入密钥(C/C) 以加密导入ECDH密钥对为例&#xff0c;涉及业务侧加密密钥的[密钥生成]、[协商]等操作不在本示例中体现。 具体的场景介绍及支持的算法规格。 在CMake脚本中链接相关动态库 target_link_libraries(entry PUBLIC libhuks_ndk.z.so)开发步骤 设备A&#xf…

【日记】我就是世界上最幸福的人!(1124 字)

正文 今天想写的内容有点多&#xff0c;就不写在纸上了。 首先&#xff0c;最高兴的&#xff0c;还是我们的《艾尔登法环》有了进展。我和兄长终于通过了 “火山官邸&#xff1a;地底拷问所”。我真是不知道&#xff0c;我和他在这个地方被那两个掳人少女人拷问了多少次了。不仅…

笔记本电脑投屏怎么操作?一看就会!

日常工作或办公都会用到笔记本电脑&#xff0c;但很多新手用户不知道笔记本电脑的投屏要怎么操作&#xff1f;接下来系统之家给大家介绍三种简单的操作方法&#xff0c;帮助大家轻松完成笔记本电脑投屏投屏操作&#xff0c;从而满足自己的办公或学习使用需求。 方法一 1. 直接W…