项目亮点之Mybatis

#原始的JDBC操作:

##原始JDBC源代码:

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

/**
 * @ClassName: TestJDBC
 * @Description: TODO(原始的JDBC操作数据库)
 */
public class TestJDBC {
    public static void main(String[] args) {

        // 数据库连接
        Connection connection = null;
        // 预编译的Statement,使用预编译的Statement提高数据库性能
        PreparedStatement preparedStatement = null;
        // 结果集
        ResultSet resultSet = null;

        try {
            // 加载数据库驱动
            Class.forName("com.mysql.cj.jdbc.Driver");

            // 通过驱动管理类获取数据库链接
            connection = DriverManager
                    .getConnection(
                            "jdbc:mysql://localhost:3306/toutiao?characterEncoding=utf-8",
                            "root", "xxh171015");
            // 定义sql语句 ?表示占位符
            String sql = "select * from user where name = ?";
            // 获取预处理statement
            preparedStatement = connection.prepareStatement(sql);
            // 设置参数,第一个参数为sql语句中参数的序号(从1开始),第二个参数为设置的参数值
            preparedStatement.setString(1, "x");
            // 向数据库发出sql执行查询,查询出结果集
            resultSet = preparedStatement.executeQuery();
            // 遍历查询结果集
            while (resultSet.next()) {
                System.out.println("id:"+resultSet.getString("id") + "  name:"
                        + resultSet.getString("name")+" head_url:"+resultSet.getString("head_url"));
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 释放资源
            if (resultSet != null) {
                try {
                    resultSet.close();
                } catch (SQLException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
            if (preparedStatement != null) {
                try {
                    preparedStatement.close();
                } catch (SQLException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
            if (connection != null) {
                try {
                    connection.close();
                } catch (SQLException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }

        }

    }
}

##存在的问题

1、数据库连接,使用时就创建,不使用立即释放,对数据库进行频繁连接开启和关闭,造成数据库资源浪费,影响数据库性能。

解决方案:使用数据库连接池管理数据库连接

2、将sql语句硬编码到java代码中,如果sql 语句修改,需要重新编译java代码,不利于系统维护。

解决方案:将sql语句配置在xml配置文件中,即使sql变化,不需要对java代码进行重新编译

3、向preparedStatement中设置参数,对占位符号位置和设置参数值,硬编码在java代码中,不利于系统维护。

解决方案:将sql语句及占位符号和参数全部配置在xml中

4、从resultSet中遍历结果集数据时,存在硬编码,将获取表的字段进行硬编码,,不利于系统维护。

解决方案:将查询的结果集,自动映射成java对象

MyBatis是什么?

MyBatis是一个优秀的持久层ORM(Object Relational Mapping)框架,它对jdbc的操作数据库的过程进行封装,使开发者只需要关注 SQL 本身,而不需要花费精力去处理例如注册驱动创建connection创建statement手动设置参数结果集检索等jdbc繁杂的过程代码。

Mybatis通过==xml==或注解的方式将要执行的各种statement(statement、preparedStatemnt、CallableStatement)配置起来,并通过java对象和statement中的sql进行映射生成最终执行的sql语句,最后由mybatis框架执行sql并将结果映射成java对象并返回

##MyBatis架构图

7378149-dff06e3ae5947d57

架构图简单讲解:

1、mybatis配置SqlMapConfig.xml,此文件作为mybatis的全局配置文件,配置了mybatis的运行环境等信息。

mapper.xml文件即sql映射文件,文件中配置了操作数据库的sql语句。此文件需要在SqlMapConfig.xml中加载;

2、通过mybatis环境等配置信息构造SqlSessionFactory即会话工厂;

3、由会话工厂创建sqlSession即会话,操作数据库需要通过sqlSession进行

4、mybatis底层自定义了Executor执行器接口操作数据库,Executor接口有两个实现,一个是基本执行器、一个是缓存执行器;

5、Mapped Statement也是mybatis一个底层封装对象,它包装了mybatis配置信息及sql映射信息等。mapper.xml文件中一个sql对应一个Mapped Statement对象,sql的id即是Mapped statement的id;

6、Mapped Statement对sql执行输入参数进行定义,包括HashMap、基本类型、pojo,Executor通过Mapped Statement在执行sql前将输入的java对象映射至sql中,输入参数映射就是jdbc编程中对preparedStatement设置参数

7、Mapped Statement对sql执行输出结果进行定义,包括HashMap、基本类型(String、Integer)、pojo,Executor通过Mapped Statement在执行sql后将输出结果映射至java对象中,输出结果映射过程相当于jdbc编程中对结果的解析处理过程

简单举例:

这个例子中使用注解的方式使用mybatis

拿UserService中的登录功能举例:

    public Map<String, Object> login(String username, String password){
        Map<String,Object> map=new HashMap<String,Object>();
        if (StringUtils.isBlank(username)){
            map.put("msgname","用户名不能为空");
            return map;
        }
        if (StringUtils.isBlank(password)){
            map.put("msgpwd","密码不能为空");
            return map;
        }
      //这句是我们要注意的,用到了被依赖注入的userDAO中的selectByName()方法
        User user = userDAO.selectByName(username);
        if(user==null){
            map.put("msgname","用户名不存在");
            return map;
        }
        if(!ToutiaoUtil.MD5(password+user.getSalt()).equals(user.getPassword())){
            map.put("msgpwd","密码不正确");
            return map;
        }
        map.put("userId", user.getId());

        String ticket=addLoginTicket(user.getId());
        map.put("ticket",ticket);
        return map;
    }

UserDAO中:

    String TABLE_NAME = "user";
    String INSET_FIELDS = " name, password, salt, head_url ";
    String SELECT_FIELDS = " id, name, password, salt, head_url";
@Select({"select ", SELECT_FIELDS, " from ", TABLE_NAME, " where id=#{id}"})
    User selectById(int id);
  1. 上面中@Select注解中一个sql对应一个Mapped Statement对象,Mapped Statement对sql执行输入参数进行定义,包括HashMap、基本类型、pojo(java对象),这里的输入对象是(Integer)id

  2. Executor通过Mapped Statement在执行sql前将输入的java对象映射至sql中,输入参数映射就是jdbc编程中对preparedStatement设置参数(这步中的映射操作是,上述由@Select注解的内容,经过java对象最后映射成了sql语句,其实就是将#{id}映射成方法中的形参id);然后执行映射后的sql语句;

会被映射成如下:

select id, name, password, salt, head_url from user where id=(方法中传入的id)
  1. Executor执行完sql后,会将输出结果映射至java对象中,输出结果映射过程相当于jdbc编程中对结果的解析处理过程

    我们从UserDAO的selectById()方法中就可以看出来,最后返回的是User对象。

MyBatis的执行流程

v2-771a68ce10ea003a11e7fd7751bec30c_1200x500

1 Resources

首先Mybaitis自己的一个Resources类会去调用一个叫getResourceAsStream()的方法

加载配置MyBatis的核心配置文件,得到一个流对象(InputStream类)。(本质上还是走的java.lang包下的ClassLoader类加载器的getResourceAsStream)

是在mybatis自己的包下:

package org.apache.ibatis.io;
/*
   * Returns a resource on the classpath as a Stream object
   *
   * @param resource The resource to find
   * @return The resource
   * @throws java.io.IOException If the resource cannot be found or read
   */
  public static InputStream getResourceAsStream(String resource) throws IOException {
    return getResourceAsStream(null, resource);
  }
/*
   * Returns a resource on the classpath as a Stream object
   *
   * @param loader   The classloader used to fetch the resource
   * @param resource The resource to find
   * @return The resource
   * @throws java.io.IOException If the resource cannot be found or read
   */
  public static InputStream getResourceAsStream(ClassLoader loader, String resource) throws IOException {
    InputStream in = classLoaderWrapper.getResourceAsStream(resource, loader);
    if (in ** null) {
      throw new IOException("Could not find resource " + resource);
    }
    return in;
  }
package java.lang;
public abstract class ClassLoader {
public InputStream getResourceAsStream(String name) {
        URL url = getResource(name);
        try {
            return url != null ? url.openStream() : null;
        } catch (IOException e) {
            return null;
        }
    }
}

2 SqlSessionFactoryBuilder

有了这个流对象,下面执行这个方法

package org.apache.ibatis.session;
public class SqlSessionManager implements SqlSessionFactory, SqlSession {
public static SqlSessionManager newInstance(InputStream inputStream) {
    return new SqlSessionManager(new SqlSessionFactoryBuilder().build(inputStream, null, null));
  }
}
package org.apache.ibatis.session;
public class SqlSessionFactoryBuilder {
public SqlSessionFactory build(InputStream inputStream) {
    return build(inputStream, null, null);
  }
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    try {
      XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
      return build(parser.parse());
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error building SqlSession.", e);
    } finally {
      ErrorContext.instance().reset();
      try {
        inputStream.close();
      } catch (IOException e) {
        // Intentionally ignore. Prefer previous error.
      }
    }
  }
  public SqlSessionFactory build(Configuration config) {
    return new DefaultSqlSessionFactory(config);
  }
}

会先去创建一个SqlSessionFactoryBuilder对象(构建者对象),调用build()方法,传入第一步我们得到的那个流对象

在这个过程中,首先,MyBatis会先去创建一个XMLConfigBuilder解析器对象

XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
return build(parser.parse());

解析我们刚才传进来的那个流对象,

package org.apache.ibatis.builder.xml;
public class XMLConfigBuilder extends BaseBuilder {

public Configuration parse() {
    if (parsed) {
      throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    parsed = true;
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
  }

}

解析完成之后,返回一个configuration对象,这个对象里面封装了解析之后我们配置的信息

正好这个configuration对象,就是我们这个方法创建(I)SqlSessionFactory类型对象需要的参数

public SqlSessionFactory build(Configuration config) {
    return new DefaultSqlSessionFactory(config);
  }

返回的就是一个实现了SqlSessionFactory接口的==DefaultSqlSessionFactory==对象

3 SqlSesionFactory

有了这个SqlSesionFactory实现类的对象之后,我们调用openSession()的方法,这个过程中

package org.apache.ibatis.session.defaults;
public class DefaultSqlSessionFactory implements SqlSessionFactory {

public SqlSession openSession() {
    return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
  }

  private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    try {
      final Environment environment = configuration.getEnvironment();
      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
      tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
      final Executor executor = configuration.newExecutor(tx, execType);
      return new DefaultSqlSession(configuration, executor, autoCommit);
    } catch (Exception e) {
      closeTransaction(tx); // may have fetched a connection so lets call close()
      throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

}

需 要 通 过TransactionFactory 生成 Transaction 对象, 并且还需要创建核心执行器 Executor对象,基于这些条件,最终创建了实现SqlSession接口的DefaultSqlSession对象

4 SqlSession

之后, 通过 SqlSession 对象执行相应的操作, 如果执行成功, 调用 commit 方法提交事务; 如果失败, 调用rollback 方法事务回滚. 最后, 调用 close 方法关闭session 资源.

##总结 MyBatis运行过程中涉及到的类和接口:

Resources(C) 类

用于加载MyBatis核心配置文件

XMLConfigBuilder(C) 类

用于解析xml文件(核心配置文件)

Configuration(C)类

用于存放xml文件解析后的结果

DefaultSqlSessionFactory(C)类

是SqlSessionFactory(I)的实现类,创建时需要使用Configruation对象作为参数

SqlSession(I)接口

是MyBatis操作的核心

DefaultSqlSession(C)类

是SqlSession接口的实现类

TransactionFactory(I)接口

用于生产Trasaction对象(关于事务的)

Transaction(I) 接口

用于表示操作数据库的事务对象

Executor(I)接口

是MyBatis的核心执行器,类似于JDBC中的Statement,常用的实现类是SimpleExecutor


   转载规则


《项目亮点之Mybatis》 xuxinghua 采用 知识共享署名 4.0 国际许可协议 进行许可。
  目录
I I