Mybatis框架学习笔记part1

前言

mybatis框架是我学习的第一个框架
框架即是许多工具类的集合
让我们更关心于代码的逻辑 需求实现的方法 省去了很多繁琐的重复的步骤

Mybatis

Mybatis是一个优秀的基于Java的持久层框架,它内部封装了Jdbc,使开发者只需要关注sql语句本身,而不需要花费精力去处理加载驱动、创建连接、创建statement等繁杂的过程。

public static void main(String[] args) throws ClassNotFoundException, SQLException {
        //注册驱动
        Class.forName("com.mysql.jdbc.Driver");
        //获取连接
        Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/itpy_mybatis", "root", "root");
        //准备sql 获取prepareStatement
        PreparedStatement statement = connection.prepareStatement("select * from user");
        //执行sql 获取resultSet
        ResultSet resultSet = statement.executeQuery();
        //处理结果
        ArrayList<User> users = new ArrayList<>();
        while (resultSet.next()){
            User user = new User();
            Object id = resultSet.getObject("id");
            user.setId((Integer) id);
            Object username = resultSet.getObject("username");
            user.setUsername((String) username);
            Object gender = resultSet.getObject("gender");
            user.setGender((String) gender);
            Object birthday = resultSet.getObject("birthday");
            user.setBirthday((java.util.Date) birthday);
            Object address = resultSet.getObject("address");
            user.setAddress((String) address);
            users.add(user);
        }
        //关闭资源
        connection.close();
        statement.close();
        System.out.println(users);

    }

显然若仅仅使用jdbc编程步骤十分繁琐且不便于修改
并且大部分代码都是固定的 只需要修改其中某些部分而已
故我们尝试封装代码来减少代码编写长度

尝试自定义编写框架

1.使用工厂模式以及构建者模式构建框架
首先创建一个SqlSession接口
其中提供了各种增删改查的抽象方法以供实现

public interface SqlSession {
    <T>List<T> findAll(String statement);
}

SqlSession接口的默认实现类

public class DefaultSqlSession implements SqlSession {
    @Override
    public <T> List<T> findAll(String statement) {
    	return null;
    }
}

创建SqlSession接口的实现类需要用到工厂类
提供openSession方法来创建SqlSession接口的实现类

public class SqlSessionFactory {
    public SqlSession openSession(){
        DefaultSqlSession DefaultSqlSession = new DefaultSqlSession(configuration);
        return DefaultSqlSession;
    }
}

SqlSessionFactoryBuilder用来构建工厂类
许多必须完成的操作可用放在此类中 如配置文件的解析及结果对象的封装

public class SqlSessionFactoryBuilder {
    //默认选项
    public SqlSessionFactory build(){
        return null;
    }

    //文件改了位置
    public SqlSessionFactory build(String path){
       return null;
    }

    //直接给一个流
    public SqlSessionFactory build(InputStream inputStream){
        return null;
    }

    private void loadMapperXml(String location, HashMap<String, Sqlmap> sqls) {
    }
    private DataSource loadDataSource(Document document) {
        return null;
    }
}

框架的工作流程大致是获取配置文件中的dataSource连接池对象以及sql语句
在实现类中从连接池获取连接调用prepareStatement方法传入sql语句进行执行
再对查询到的结果封装
故关键就是获取DataSource对象和sql语句
所有我们把它们封装成一个实体类configuration的两个属性 以便作为参数传输

public class Configuration {
    private DataSource dataSource;
    private Map<String, Sqlmap> sqlMaps;

    public Map<String, Sqlmap> getSqlMaps() {
        return sqlMaps;
    }

    public void setSqlMaps(Map<String, Sqlmap> sqlMaps) {
        this.sqlMaps = sqlMaps;
    }

    public DataSource getDataSource() {
        return dataSource;
    }

    public void setDataSource(DataSource dataSource) {
        this.dataSource = dataSource;
    }
}
public class Sqlmap {
    private String sql;
    private Class clazz;

    public Sqlmap(String sql, Class clazz) {
        this.sql = sql;
        this.clazz = clazz;
    }

    public Sqlmap() {
    }

    public Class getClazz() {
        return clazz;
    }

    public void setClazz(Class clazz) {
        this.clazz = clazz;
    }

    public String getSql() {
        return sql;
    }

    public void setSql(String sql) {
        this.sql = sql;
    }
}

配置文件:
SqlMapConfig.xml:

<?xml version="1.0" encoding="UTF-8" ?>
<configuration>
   <environments default="development">
      <environment id="development">
        <transactionManager type="JDBC" />
        <dataSource type="POOLED">
          <property name="driver" value="com.mysql.jdbc.Driver" />
          <property name="url" value="jdbc:mysql://127.0.0.1:3306/itpy_mybatis?characterEncoding=utf8" />
          <property name="username" value="root" />
          <property name="password" value="root" />
        </dataSource>
     </environment>
  </environments>
    <mappers>
        <mapper resource="UserMapper.xml"></mapper>
        <mapper resource="ProductMapper.xml"></mapper>
    </mappers>
</configuration>

UserMapper.xml:

<?xml version="1.0" encoding="utf-8" ?>
<mapper namespace="user">
	<select id="findAll1" resultType="com.itpy.domain.User">
		select * from user limit 0,2
	</select>
	<select id="findAll2" resultType="com.itpy.domain.User">
		select * from user limit 2,2
	</select>
</mapper>

2.代码补完
SqlSessionFactoryBuilder是最先被创建出来的 所以我们在这个类中封装解析配置文件的操作

public class SqlSessionFactoryBuilder {
    //默认选项
    public SqlSessionFactory build(){
        InputStream resourceAsStream = SqlSessionFactoryBuilder.class.getClassLoader().getResourceAsStream("SqlMapConfig.xml");
        return build(resourceAsStream);
    }

    //文件改了位置
    public SqlSessionFactory build(String path){
        InputStream resourceAsStream = SqlSessionFactoryBuilder.class.getClassLoader().getResourceAsStream(path);
        return build(resourceAsStream);
    }

    //直接给一个流
    public SqlSessionFactory build(InputStream inputStream){
        //开始解析xml配置文件
        SAXReader saxReader = new SAXReader();
        try {
            Document document = saxReader.read(inputStream);
            //数据库连接池的相关解析
            DataSource dataSource = loadDataSource(document);
            //sql语句相关解析
            Map<String, Sqlmap> sqls = loadSqls(document);

            Configuration configuration = new Configuration();
            configuration.setDataSource(dataSource);
            configuration.setSqlMaps(sqls);
            SqlSessionFactory sqlSessionFactory = new SqlSessionFactory();
            sqlSessionFactory.setConfiguration(configuration);
            return sqlSessionFactory;
        } catch (DocumentException e) {
            e.printStackTrace();
        }finally {
            try {
                inputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return null;
    }

    private Map<String,Sqlmap> loadSqls(Document document) {
        List<Element> list = document.selectNodes("//mapper");
        HashMap<String, Sqlmap> sqls = new HashMap<>();
        for (Element element : list) {
            //获取元素的resource属性 代表另外的配置文件位置
            String location = element.attributeValue("resource");
            loadMapperXml(location,sqls);
        }
        return sqls;
    }

    private void loadMapperXml(String location, HashMap<String, Sqlmap> sqls) {
        InputStream resourceAsStream = SqlSessionFactoryBuilder.class.getClassLoader().getResourceAsStream(location);
        SAXReader saxReader = new SAXReader();
        Document document = null;
        try {
            document = saxReader.read(resourceAsStream);
            //获取namespace
            String namespace = document.getRootElement().attributeValue("namespace");
            List<Element> list = document.selectNodes("//select");
            for (Element element : list) {
                String id = element.attributeValue("id");
                String key = namespace+"."+id;
                String resultType = element.attributeValue("resultType");
                Class<?> aClass = Class.forName(resultType);
                String sql = element.getTextTrim();

                sqls.put(key,new Sqlmap(sql,aClass));
            }
        } catch (DocumentException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            throw new RuntimeException("找不到类型",e.getException());
        }


    }


    private DataSource loadDataSource(Document document) {
        List<Element> list = document.selectNodes("//property");
        HikariConfig config = new HikariConfig();
        for (Element element : list) {
            String name = element.attributeValue("name");
            if ("driver".equals(name)){
                String value = element.attributeValue("value");
                config.setDriverClassName(value);
            }
            if ("url".equals(name)){
                String value = element.attributeValue("value");
                config.setJdbcUrl(value);
            }
            if ("username".equals(name)){
                String value = element.attributeValue("value");
                config.setUsername(value);
            }
            if ("password".equals(name)){
                String value = element.attributeValue("value");
                config.setPassword(value);
            }
        }
        HikariDataSource dataSource = new HikariDataSource(config);
        return dataSource;
    }
}

数据库连接池解析loadDataSource时 没有使用c3p0连接池 使用的是HikariCP连接池

解析完成后 Configuration 在创建SqlSessionFactory类时也应传给SqlSessionFactory
故SqlSessionFactory类:

public class SqlSessionFactory {
    private Configuration configuration;

    public Configuration getConfiguration() {
        return configuration;
    }
    public void setConfiguration(Configuration configuration) {
        this.configuration = configuration;
    }
    public SqlSession openSession(){
        DefaultSqlSession DefaultSqlSession = new DefaultSqlSession(configuration);
        return DefaultSqlSession;
    }
}

同样DefaultSqlSession的构造 也应传入此对象
DefaultSqlSession的作用主要是从连接池获取连接执行sql语句并把结果封装

public class DefaultSqlSession implements SqlSession {
    private Configuration configuration;

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

    @Override
    public <T> List<T> findAll(String statement) {
        //获取连接 使用数据连接池方案
        try {
            Connection connection = configuration.getDataSource().getConnection();
            //获取prepareStatement(包含sql)
            //获取sql语句
            Sqlmap sqlmap = configuration.getSqlMaps().get(statement);
            PreparedStatement preparedStatement = connection.prepareStatement(sqlmap.getSql());
            ResultSet resultSet = preparedStatement.executeQuery();
            //拿到我们想要封装成的类型
            Class clazz = sqlmap.getClazz();
            //处理结果 封装
            List list = handleResultset(clazz,resultSet);
            return list;
        } catch (Exception throwables) {
            throwables.printStackTrace();
        }
        return null;
    }

    private List handleResultset(Class clazz, ResultSet resultSet) throws SQLException, IllegalAccessException, InstantiationException {
        List list = new ArrayList();
        //想要一个clazz类型的list集合
        //遍历resultset 封装到集合中
        //获取元信息
        ResultSetMetaData metaData = resultSet.getMetaData();
        int columnCount = metaData.getColumnCount();
        //想办法获取所有列的名字
        List<String> columnNames = new ArrayList<>();
        for (int i = 1; i <= columnCount; i++) {
            columnNames.add(metaData.getColumnName(i));
        }

        while (resultSet.next()){
            Object o = clazz.newInstance();
            for (String columnName : columnNames) {
                Object columnValue = resultSet.getObject(columnName);
                ReflectorUtil.set(o,columnName,columnValue);
            }
            list.add(o);
        }

        return list;
    }
}

其中ReflectorUtil是一个工具类 作用是把查询到的列名和其对应的内容拼装成一个实体类

public class ReflectorUtil {
    private static Map<Class,BeanMethodInfo> cache=new HashMap<>();

    
    private static BeanMethodInfo getBeanMethodInfo(Object o){
        Class<?> oClass = o.getClass();
        BeanMethodInfo beanMethodInfo = cache.get(oClass);
        if (beanMethodInfo==null){
            beanMethodInfo = new BeanMethodInfo(o.getClass());
            cache.put(oClass,beanMethodInfo);
        }
        return beanMethodInfo;

    }
    public static void set(Object o,String propertyName,Object propertyValue){
        BeanMethodInfo beanMethodInfo = getBeanMethodInfo(o);
        Method method = beanMethodInfo.setterMethod(propertyName);
        try {
            method.invoke(o,propertyValue);
        } catch (Exception e) {
            throw new RuntimeException("set--"+propertyName+"--property failed!",e);
        }
    }
    public static Object get(Object o,String propertyName){
        BeanMethodInfo beanMethodInfo = getBeanMethodInfo(o);
        Method method = beanMethodInfo.getterMethod(propertyName);
        try {
            return method.invoke(o);
        } catch (Exception e) {
            throw new RuntimeException("get--"+propertyName+"--property failed!",e);
        }
    }

    private static final class BeanMethodInfo{
        private Class clazz;
        private Map<String,Method> setterMethods=new HashMap<>();
        private Map<String,Method> getterMethods=new HashMap<>();

        public BeanMethodInfo(Class clazz) {
            this.clazz = clazz;
            addGetterMethods();
            addSetterMethods();
        }
        public Method setterMethod(String property){
            return setterMethods.get(property);
        }
        public Method getterMethod(String property){
            return getterMethods.get(property);
        }
        private  String methodToProperty(String name) {
            if(name.startsWith("is")) {
                name = name.substring(2);
            } else {
                if(!name.startsWith("get") && !name.startsWith("set")) {
                    throw new RuntimeException("Error parsing property name \'" + name + "\'.  Didn\'t start with \'is\', \'get\' or \'set\'.");
                }
                name = name.substring(3);
            }
            if(name.length() == 1 || name.length() > 1 && !Character.isUpperCase(name.charAt(1))) {
                name = name.substring(0, 1).toLowerCase() + name.substring(1);
            }
            return name;
        }
        private void addSetterMethods(){
            Method[] methods = clazz.getMethods();
            int len = methods.length;

            for(int i = 0; i < len; i++) {
                Method method = methods[i];
                String name = method.getName();
                if(name.startsWith("set") && name.length() > 3 && method.getParameterTypes().length == 1) {
                    String property = methodToProperty(name);
                    setterMethods.put(property,method);
                }
            }
        }
        private void addGetterMethods(){
            Method[] methods = clazz.getMethods();
            int len = methods.length;

            for(int i = 0; i < len; i++) {
                Method method = methods[i];
                String name = method.getName();
                if(name.startsWith("get") && name.length() > 3 || name.startsWith("is") && name.length() > 2) {
                    String property = methodToProperty(name);
                    getterMethods.put(property,method);
                }
            }

        }
    }
}

这样我们就自定义完成了一个简单的框架 有利于我们理解mybatis框架的原理

public class AppTest {
    @Test
    public void test(){
        SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
        SqlSessionFactory sqlSessionFactory = builder.build();
        SqlSession sqlSession = sqlSessionFactory.openSession();
        List<User> list = sqlSession.findAll("user.findAll1");
        System.out.println(list);
    }
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值