初识mybatis2019-12-31

什么是mybatis

我们知道一个框架的出现,对应都解决某个问题的方案,而mybatis框架的存在,是为了解决持久层的问题,后面还会学到springMVC,解决的是表现层的问题

目前解决持久层的方案有:

  1. JDBC:
  2. Spring的JDBCTemplate(对JDBC进行简单的封装)
  3. Apache的DBUtils(和Spring的JDBCTemplate很想,也是对JDBC进行简单的封装)
  4. mybatis

但以上1、2、3三个都不是框架!JDBC是规范,其余两个是对规范的实现,是工具类,并没有一套完美的解决方案。

简单的jdbc的步骤里是这样:

Class.forName("com.mysql.jdbc.Driver"); // 对应的数据库驱动
Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8","karasawa","pwd");
String sql = "seleclt * from user where name = ?";
PreparedStatement stm = connection.preparedStatement(sql);
ResultSet res = stm.setString(1,"karasawa");
stm.close();
connection.close();
return res;

以上代码学过jdbc的都看得懂,当我们查询的东西一多,这些步骤将会重复,很多东西对我们来说变得繁琐(我们的目的就是用sql语句进行增删改查)而mybatis的出现,使得我们只需关注sql语句本身,实现需求,完成高效率的开发。

_mybatis是一个基于java的持久层框架,内部封装了JDBC(用ORM思想封装结果集,Object Relational Mapping:对象关系映射,通过实例对象的语法,完成关系型数据库的操作的技术),使得开发者只需要关注sql语句本身,不需要花费经历去处理加载驱动,创建连接、statement的繁杂的过程,当然,在我们做到只关注sql语句之前,还得先完成一些简单的配置

三层架构

mybatis分为了三层架构

1.表现层用来展示数据

2.业务层进行业务需求的处理

3.持久层是和数据库交互的

步骤

使用mybatis需要以下步骤(maven工程):

  1. 创建maven工程并导入对应的依赖
  2. 创建实体类和dao的接口
  3. 创建mybatis的创建文件(xml)
  4. 创建映射配置文件(给单一的dao类用)

mybatis中会把持久层的操作接口名称和映射文件都叫做Mapper,所以SingerDao和SingerMapper是一样的 mybatis的映射配置文件必须和dao接口的包结构相同 映射配置文件的mapper标签namespace属性的取值必须是dao接口的全限定类名 映射配置文件的操作配置,id属性的取值必须是dao接口的方法名

遵守以上几点后,在开发中就无需再写Dao的实现类了(牛逼!!)

先看看小案例

讲了这么多,听的云里雾里,先上个小案例来瞅瞅!

创建maven工程并导入对应的依赖。在我的数据库里有一个myface表,数据如下:

gnU9wL

创建实体类和dao的接口:

fIPTrC

// Singer类就不贴出来了

package com.karasawa.kim.mybatis.dao;

import com.karasawa.kim.mybatis.common.Singer;
import java.util.List;

public interface SingerDao {

  /**
   * 查询singer类中的所有数据
    * @return
   */
  List<Singer> findAll();
}

创建mybatis的创建主配置文件和映射配置文件:

pEMPAL


<!-- SqlMapConfig.xml -->

<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE configuration
  PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-config.dtd">

<!--mybatis的主配置文件-->
<configuration>
  <!--配置环境-->
  <environments default="mysql">
    <!--配置mysql环境-->
    <environment id="mysql">
      <!--事务类型-->
      <transactionManager type="JDBC"/>
      <!--配置数据源(连接池)-->
      <dataSource type="POOLED">
        <!--配置连接数据库的4个基本信息-->
        <property name="driver" value="com.mysql.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/myface"/>
        <property name="username" value="UserName"/>
        <property name="password" value="password"/>
      </dataSource>
    </environment>
  </environments>
<!--  除了主配置,还可以在这里配置文件的映射,映射配置文件指的是每个dao独立的配置文件-->
  <mappers>
    <mapper resource="com/karasawa/kim/dao/SingerDao.xml"/>
  </mappers>
</configuration>

<!-- SingerDao.xml -->

<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE mapper
  PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.karasawa.kim.mybatis.dao.SingerDao">
<!--  配置查询所有-->
    <select id="findAll" resultType="com.karasawa.kim.mybatis.common.Singer">
      select * from singer
    </select>
</mapper>

这样mybatis就配置好了,创建一个测试类,在main中写上以下代码:

package com.karasawa.kim.mybatis;

import com.karasawa.kim.mybatis.common.Singer;
import com.karasawa.kim.mybatis.dao.SingerDao;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

/**
 * @program: mybatisPratice
 * @description: 入门案例
 * @author: karasawa
 * @create: 2019-12-31 20:59
 **/
public class MybatisTest {

  @Test
  public void testQuery() throws IOException {
//    读取配置文件
    InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml");
//    创建SqlSessionFactory工厂
    SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
    SqlSessionFactory factory = builder.build(in);
//    使用工厂生产SqlSession对象
    SqlSession session = factory.openSession();
//    使用SqlSession创建Dao接口的代理对象
    SingerDao singerdao = session.getMapper(SingerDao.class);
//    使用代理 对象执行方法
    List<Singer> singers = singerdao.findAll();
//    释放资源
    for (Singer singer : singers) {
      System.out.println(singer);
    }
    session.close();
    in.close();
  }
}

运行后就拿到数据了

gonWq9

解析mybatis流程中的设计模式分析

让我们对上面的案例解析一下:

测试代码解析

  1. 读取配置文件

    通过Resources.getResourceAsStream("要读取的名字")来获取配置文件。路径有着绝对路径和相对路径,可是在实际开发中,这两种变得不靠谱,绝对路径就不说了,相对路径,在部署完后目录结构会发生变化,因此相对目录也不靠谱了,在实际开发中,更多用的是:1. 使用类加载器,2. 使用ServletContext对象的getRealPath()。在这里用的是类加载器,只在用到的时候再加载,加载出来的路径是在对应情况下的绝对路径。

  2. 创建SqlSessionFactory工厂

    这里mybatis用了构建者模式(你不会造房子,找来了包工头,所有东西都给他做,你只需要告诉他要什么样房子,其他细节都不需要你管,这里SqlSessionFactoryBuilder对象是包工头,你只需要给钱,通过该对象的build(in)方法传进入,in就是钱)

  3. 使用工厂生产SqlSession对象

    用工厂模式模式创建SqlSession对象,工厂模式是一个设计模式,可以达到解耦的效果,降低类之间的依赖,后面会写一个笔记出来

  4. 使用SqlSession创建Dao接口的代理对象(session.getMapper()方法)

    创建Dao接口的实现类,使用了代理模式,能在不修改源码的基础上对已有的方法增加,这也是为什么不需要写实体类的原因

刚开始接触的时候,觉得有些麻烦多余,在明明可以直接读取配置然后getMapper,再执行对应方法就行 ,以很少的代码完成同样的功能,但mybatis怎么多了这么多代码?其实不然,像build()和openSession()都有很多种重载可选,mybatis这样设计大大增加了灵活性,不会写死,可以看到在这里基本都用了设计模式,其实到了这个层次,设计模式是很常见的,如果看不懂,还是去学一点设计模式吧,简单工厂模式之类的,spring源码的核心是IOC和AOP,解析源码的时候会发现一大推设计模式...

xml/annotation

mybatis的配置有两种,xml和annotation(注解),在主配置文件中都需要mapper标签,一个是放resource属性,一个是放class属性。

我上面用的是第一种,xml配置还需要为每个dao类创建一个xml,放resource属性里,像这样:

  <mappers>
    <mapper resource="com/karasawa/kim/dao/SingerDao.xml"/>
  </mappers>

用注解,则省去了创建单个xml文件,但需要在dao类中添加注解,值(全限定类名)是放在class里,像这样:

public interface SingerDao {

    //添加这个注解,用@Select来标明这是一个select语句
    @Select("select * from singer")
    List<Singer> findAll();
}
  <mappers>
    <mapper class="com.karasawa.kim.mybatis.dao.SingerDao"/>
  </mappers>

修改成注解模式后,运行得到同样的数据,这里就感到mybatis的牛逼了,居然不用创建实体类!(当然要创建也可以,但感觉没意义=-=,这里就不写出来了),关于用XML还是注解,官方推荐用XML

使用代理dao

mybatis在使用代理dao去增删改查时,做了什么事?

  1. 创建代理对象
  2. 在代理对象中调用selectList

这是连接数据库的配置,有了这个数据源就能创建connection对象

<dataSource type="POOLED">
    <!--配置连接数据库的4个基本信息-->
    <property name="driver" value="com.mysql.jdbc.Driver"/>
    <property name="url" value="jdbc:mysql://localhost:3306/myface"/>
    <property name="username" value="root"/>
    <property name="password" value="yourPassword"/>
</dataSource>

还有这个配置,这个配置信息是用来配置映射的

<mappers>
    <mapper resource="com/karasawa/kim/dao/SingerDao.xml"/>
</mappers>

解析到这行的时候,会这到这里所提供的url去解析这些xml

<mapper namespace="com.karasawa.kim.mybatis.dao.SingerDao">
<!--  配置查询所有-->
    <select id="findAll" resultType="com.karasawa.kim.mybatis.common.Singer">
      select * from singer
    </select>
</mapper>

解析完这个配置信息,就有了sql语句,就可以过去PreparedStament,同时还封装一个实体类全限定类名

我们继续分析一下

所有的xml标签,mybatis会用dom4j技术解析,然后将得到的参数传进selectList方法,其实selectList就是jdbc的封装了,在处理结果集这里,采用了反射封装,最后把List返回出去。

selectList方法步骤大概是这样:

// 根据配置信息创建connection对象
// 注册驱动,获取连接
// 获取预处理对象PreparedStatement(根据配置信息传入sql语句)
// 执行查询
ResultSet res = PreparedStatement.executeQuery();
// 遍历结果集用于封装
list<E> list = new ArrayList();
while(res.next()){
  E EntityClass = (E) Class.forName(在mapper里的resultType属性解析得到的全限定类名).newInstance();
  // 下面进行反射封装,把res的内容都添加到实体类(EntityClass)中

  思路是我们的实体类属性和表中的列名是一致的,于是我们就可以把表的列名看成是实体类的属性名称。就可以使用反射的方式来根据名称获取每个属性,并把值赋进去

  // 封装结束,把创建的实体类添加到list里返回出去
  EntityClass.addxxx(res.get(...));
  ...
  list.add(EntityClass);
}
// 返回list
return list

要执行selectList,需要两个部分:

  1. 连接信息
  2. 映射信息
    1. SQL语句
    2. 实体类全限定类名

一个是连接信息(数据源/连接池),另一个是映射信息,对于映射信息,又分成了SQL 语句和实体类全限定类名,mybatis把这个部分又封装成一个对象,假设叫Mapper(有nameSpace、id、sql语句、resultType),但有多个的时候,如何来区分呢?所以,mybatis用了一个容器以key:value形式来存放mapper,key是String类型,存放两个信息,一个抽象类的包名(nameSpace),一个是select标签中的id,两个字符串用逗号分开,value就是一个mapper对象。包含sql字符串和domainClassPath(实体类全限定类名)字符串。获取到这些信息后,再用getMapper创建代理对象,代理对象创建过程中也解析了XML,里面就包括了代理对象的方法所对应的sql语句,所以创建完代理对象,执行对应的方法就行了!(看完mybatis初步解析的我留下了眼泪,太棒了这!)

CRUD

CRUD(create read updata detele),也就是常说的增删改查了

比较简单这里就贴插入的代码就行了,更新和删除都是差不多的

插入

在mapper下面添加< insert >标签,values的值用#{名称}来代替,例如这样:


<!-- SingerDao.xml -->

.**值得一提的是这里的#{名称}的名称是get/set后面对应的名称**

<insert id="insertSinger" parameterType="com.karasawa.kim.mybatis.common.Singer">
  insert into singer(name,rank) values(#{name},#{rank})
</insert>

// SingerDao.java

public interface SingerDao {
  /**
   * 新插入歌手数据
   * @param singer
   */
  void insertSinger(Singer singer);
}

这里将测试文件改进一下,为了代码冗余,进行了初始化和执行完的后续操作

public class MybatisTest {

  private InputStream in;
  private SqlSession session;
  private SingerDao singerdao;

  @Before
  public void init() throws IOException {
    in = Resources.getResourceAsStream("SqlMapConfig.xml");
    SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
    SqlSessionFactory factory = builder.build(in);
    session = factory.openSession();
    singerdao = session.getMapper(SingerDao.class);
  }

  @After
  public void destroy() throws IOException {
    session.close();
    in.close();
  }

  @Test
  public void insertData(){
    Singer singer = new Singer();
    singer.setName("karasawas");
    singer.setRank(4);

    singerdao.insertSinger(singer);
    session.commit();
  }
}

模糊查询

mybatis在模糊查询的时候,因为是通过#{}来传递参数的,但模糊查询需要%,所以需要我们在传递参数前手动添加%,例如这样:

  @Test
  public void querySingerByWord(){
    // 像这样在传值的时候添加上%
    List<Singer> singers = singerdao.querySingerByWord("%kara%");
    for(Singer singer : singers){
      System.out.println(singer.getId()+"---"+singer.getName()+"---"+singer.getRank());
    }
  }

关于sql语句的用法就不说了,基本上mybatis支持

参数深入

我们在select等标签中,都可以看到一个paramsType,意思是参数的类型,这个值是一个pojo,mybatis对于pojo,用了OGNL表达式解析字段的值

OGNL表达式 Object Graphic Navigation Language对象图导航语言,它是通过对象的取值方法来获取数据。在写法上把get给省略了。比如:我们获取用户的名称 类中的写法:user.getUsername(); 0GNL表达式写法:user.username

mybatis中为什么能直接写username,而不用user.呢: 因为在parameterType中已经提供了属性所属的类,所以此时不需要写对象名

.所以在sql语句里的#{名称},就是通过解析后的值来确定的,比如新建一个User对象,里面一个一个user参数和Getter/Setter,这时候当paramsType为该类时,Sql语句中的#{username}是没有值的,只能这样表示#{user.username},这个知识点经常用在开发中进行实体类的包装

连接池

连接池是一个储存连接的容器,本质是一个集合,拥有队列的特性:先进先出。

mybatis连接池提供了三种配置,在配置文件里的< dataSource >标签,有一个type属性,可选值有三个POOLED、UNPOOLED、JNDI,三种方式代表着不同的思想

POOLED

POOLED采用传统javax.sql.DataSource规范中的连接池,mybatis中有针对规范的实现。(使用连接池的数据源)

连接池中有两种池,一种是空闲池,一种是活动池,pooled方式会先去空闲池看看有无空闲的连接,有的话直接拿出来用,判断没有的话则去活动池,先判断活动池里是否到达连接的最大数,如果没有,则new一个新的连接以队列的特点放进活动池,如果已到达最大值,则找最老的连接(队列的先进先出),然后进行一些操作使之成为一个空闲连接能为之所用

UNPOOLED

UNPOOLED采用传统的获取连接的方式,虽然也实现Javax.sql.DataSource接口,但是并没有使用池的思想。(没有使用数据源)

unpooled方式则不会使用连接池的数据源,在IDEA下ctrl+n,输入UnpooledDataSource,可以看到这几行代码

 @Override
  public Connection getConnection() throws SQLException {
    return doGetConnection(username, password);
  }

再进入doGetConnection(String username, String password)看看

  private Connection doGetConnection(String username, String password) throws SQLException {
    Properties props = new Properties();
    if (driverProperties != null) {
      props.putAll(driverProperties);
    }
    if (username != null) {
      props.setProperty("user", username);
    }
    if (password != null) {
      props.setProperty("password", password);
    }
    return doGetConnection(props);
  }

进入doGetConnection(Properties properties)

  private Connection doGetConnection(Properties properties) throws SQLException {
    initializeDriver();
    Connection connection = DriverManager.getConnection(url, properties);
    configureConnection(connection);
    return connection;
  }

看步骤是不是像极了jdbc,确实是这样,UNPOOLED方式在执行完的时候,控制台还会打印出连接close()等等信息,这种方式创建的连接并不会放入活动池,使用完毕后自己会销毁。所以UNPOOLED并没有使用数据源

JNDI

JNDI(Naming and Directory Interface)是SUN公司推出的一套规范,属于javaEE技术之一。目的是模仿window系统中的注册表。本质是一个MAP对象,key是路径+名称,value是一个对象,JNDI采用服务器提供的JNDI技术实现,来获取数据源对象,不同的服务器所能拿到数据源是不一样。(使用JNDI方式的数据源)

注意:JNDI方式如果不是web或者maven的war工程,是不能使用的。

.实际项目中我们更多使用的是POOLED,以池的思想来用mybatis

SQL语句的其他标签使用

扔个文档,偷一下懒

不过想提及一下< sql >标签

  <select id="findAll" resultType="com.karasawa.kim.mybatis.common.Singer">
      select * from singer
  </select>

  <select id="querySingerByName" parameterType="com.karasawa.kim.mybatis.common.Singer" resultType="com.karasawa.kim.mybatis.common.Singer">
    select * from singer where name = #{name}
  </select>

上面的select标签中明显有重复的sql语句片段,那就是select * from singer,开发中是很常见的,我们可以用sql标签来代替了,变成这样

  <sql id="findAllInSinger">
    select * from singer
  </sql>
  <select id="findAll" resultType="com.karasawa.kim.mybatis.common.Singer">
    <include refid="findAllInSinger"></include>
  </select>

  <select id="querySingerByName" parameterType="com.karasawa.kim.mybatis.common.Singer" resultType="com.karasawa.kim.mybatis.common.Singer">
    <include refid="findAllInSinger"></include> where name = #{name}
  </select>

看上去代码变多了?其实只是我例子中的假象,在实际开发中有更多这种重复的代码,用< includ >标签来代替是真的方便

延迟加载

什么是延迟加载:在真正使用数据时才发起查询,不用的时候不查询。按需加载(懒加载)

什么是立即加载:不管用不用,只要一调用方法,马上发起查询。

在对应的四种表关系中:一对多,多对一,一对一,多对多

一对多,多对多:通常情况下我们都是采用延迟加载。

多对一,一对一:通常情况下我们都是采用立即加载。

association/collection的延迟加载

需要先开启配置文件下添加全局的延迟加载

<settings>
  <!-- 开启Mybatis支持延迟加载 -->
  <setting name="lazyLoadingEnabled"value="true"/>
  <!-- aggressiveLazyLoading在mybatis在 3.4.1 及之前的版本默认值为 true -->
  <setting name="aggressiveLazyLoading"value="false"></setting>
</settings>

然后在association/collection添加属性select和column属性,select属性的值是对应Dao配置文件下的全限定类名+ID,column则是对应的列值,这样就可以实现association的延迟加载了

缓存

缓存是存在于内存中的临时数据,使用缓存的原因是可以减少和数据库的交互次数,提高执行效率。

什么样的数据能使用缓存,有以下标准:

  1. 经常查询并且不经常改变的。
  2. 数据的正确与否对最终结果影响不大的

Mybatis中有一级缓存和二级缓存

一级缓存

它指的是Mybatis中SqlSession对象的缓存。当我们执行查询之后,查询的结果会同时存入到SqlSession为我们提供一块区域中。该区域的结构是一个Map。当我们再次查询同样数据,mybatis会先去sqlsession中查询是否有,有的话直接拿出来用。当SqlSession对象消失时,mybatis的一级缓存也就消失了。以下情况的时候一级缓存也会清空:

.当调用Soqlsesion的修改,添加,删除,commit(),close()等方法时。

二级缓存

它指的是Mybatis中SqlSessionFactory对象的缓存。由同一个SqlSessionFactory对象创建的SalSession共享其绣存。

二级缓存的使用步骤:

  1. 让Mybatis框架支持二级缓存(在mybatis的主配置文件中配置:< settings >添加名为cacheEable的设置)
  2. 让当前的映射文件支持二级缓存(在对应dao的配置文件中配置:由< cache />标签开启)
  3. 让当前的操作支持二级缓存(在对应dao的配置文件的select标签中配置:useCache="true")

.注意,二级缓存存放的是数据,而不是对象的首地址

这次学习mybatis只是有了个了解,更多的还要有实战来巩固,开发中用的XML配置较多,故注解方式只做了了解,并没有看太多,暂时就写这么多了

学习资料参考

视频:黑马教学

文档:mybatis中文文档