Mybatis学习

Mybatis学习

什么是框架

image-20200731103405833

框架是软件开发中的一个解决方案,Mybatis框架解决的是持久层的问题,大大提高开发效率

框架封装了许多的细节,简化了开发者的操作

三层架构

  • 表现层:展示数据
  • 业务层:处理业务需求
  • 持久层:和数据库

image-20200312213033735

JdbcTemplate和dbutils封装的程度并不够,很繁琐

image-20200312221151660

ORM:Object Relational Mapping,对象关系映射,将数据库表和实体类及其属性对应起来,也就是数据库表的表项有什么,对象的属性就有什么,可以操作实体类就实现操作数据库表

JPA是orm框架标准,主流的orm框架都实现了这个标准。

MyBatis没有实现JPA,他和orm框架的设计思路完全不一样。
MyBatis是拥抱sql,而orm则更靠近面向对象,不建议写sql,实在要写推荐你写hql代替。

Mybatis是sql mapping框架而不是orm框架,当然orm和Mybatis都是持久层框架。

Hibernate是全自动ORM框架,而Mybatis是半自动的。hibernate完全可以通过对象关系模型实现对数据库的操作,拥有完整的JavaBean对象与数据库的映射结构来自动生成sql。而mybatis仅有基本的字段映射,对象数据以及对象实际关系仍然需要通过手写sql来实现和管理。

image-20200313090309987

image-20200313090805536

注意映射配置文件的位置要和dao接口包的结构相同

遵从了这样的标准,我们就无需在开发中写dao的实现类了

入门案例

创建一个Dao的接口

package com.jiading.dao;

import com.jiading.domain.User;

import java.util.List;

/**
 * 用户的持久层接口
 */
public interface IUserDao {
    List<User> findAll();
}

在resources目录下创建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"></transactionManager>
            <!-- 配置数据源,使用连接池-->
            <dataSource type="POOLED">
                <!-- 配置连接数据库的基本信息-->
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC"/>
                <property name="username" value="root"/>
                <property name="password" value="root"/>
            </dataSource>
        </environment>
    </environments>
    <!--指定映射配置文件的位置-->
    <mappers>
        <mapper resource="com/jiading/dao/IUserDao.xml"></mapper>
    </mappers>
</configuration>

datasource的type这里选择POOLED是使用连接池,它使用的是Mybatis自己实现的连接池

关于Mybatis连接池,看这篇文章:https://www.cnblogs.com/jiading/articles/13408835.html

同时创建com.jiading.dao文件夹,在其中创建IUserDao.xml文件:

映射配置文件指的是每个Dao独立的配置文件

<?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">
<!-- namespace:dao的全限定类名-->
<mapper namespace="com.jiading.dao.IUserDao">
    <!-- 配置查询所有
    id:dao方法的名称
    resultType:指定结果集的类型-->
    <select id="findAll" resultType="com.jiading.domain.User">
        select * from user;
    </select>
</mapper>

测试类:

package com.jiading.test;

import com.jiading.dao.IUserDao;
import com.jiading.domain.User;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import java.io.IOException;
import java.io.InputStream;
import java.util.List;

public class MybatisTest {
    public static void main(String[] args) throws IOException {
        //1. 读取配置文件
        InputStream in= Resources.getResourceAsStream("sqlMapConfig.xml");
        //2. 创建sqlsessionfactory
        //builder为我们创建工厂对象
        SqlSessionFactoryBuilder builder=new SqlSessionFactoryBuilder();
        SqlSessionFactory factory=builder.build(in);
        //3.使用工厂模式生产sqlSession对象
        SqlSession session=factory.openSession();
        //4. 使用sqlSession创建Dao接口的代理对象
        IUserDao userDao=session.getMapper(IUserDao.class);
        //5. 使用代理对象执行方法
        List<User> users=userDao.findAll();
        for (User user:users) {
            System.out.println(user);
        }
        //6. 释放资源
        session.close();
        in.close();
    }
}

session的getMapper是一个动态代理方法

使用builder的构建者模型,隐藏了对象的创建过程,使得使用者直接调用方法就能拿到对象,降低了对象创建的难度

getMapper使用了代理模式,使得在不修改源码的基础上可以对对象进行增强

使用工厂模式进行生产降低了类之间的耦合度

将这些对象的创建过程都暴露出来而不是封装好、只留一个接口,是为了灵活和可扩展性

步骤

image-20200731135004475

使用注解进行配置

注解配置更加高效和简便

sqlMapConfig.xml

    <!--注解配置,此处用class属性指定被注解的dao全限定类名-->
    <mappers>
        <mapper class="com.jiading.dao.IUserDao"></mapper>
    </mappers>

IUserDao.java

package com.jiading.dao;

import com.jiading.domain.User;
import org.apache.ibatis.annotations.Select;

import java.util.List;

/**
 * 用户的持久层接口
 */
public interface IUserDao {
    @Select("select * from user")
    List<User> findAll();
}

自己写实现类

实现类其实也可以自己写的,虽然没什么意义,一般大家都不自己写

image-20200731141709710

对于测试类:

image-20200731141753493

Mybatis原理分析

查询

mybatis在使用代理dao的方式实现查询时做的事是:

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

查询所有的分析

以key-value的形式存储,key是方法的全限定类名和方法名拼接起来的,value是sql和domain的path构成的mapper对象

创建代理对象

image-20200731150947249

我们在InvocationHandler的接口的实现类中调用session的selectList()方法

自己实现Mybatis框架

这个项目可以帮助我们更好地理解Mybatis的原理,项目完整的代码看这里:https://gitee.com/jiadingMayun/MyBatisSourceCodeAnalysis

Mybatis实现CRUD

这里有一点要注意,就是模糊搜索的时候:

  1. sql应该写成where xxx like #
  2. username左右应该分别有一个百分号,这个百分号应该在传入sql之前就拼接好

当然也可以写成where xxx like '%${username}%'的方式,#和$的区别可以看这篇文章:https://www.cnblogs.com/jiading/articles/12570353.html, 如果这样写的话sql就是直接拼接好的,但是这样会有sql注入的风险

插入后如何返回新建用户的id?

image-20200802104558544

这其实是用到了一个sql语句,叫select last_insert_id();

写出来就是这样:

image-20200802104918394

keyProperty对应实体类,keyColumn对应数据库表的列名

这样的话,它就会把返回的那个id值封装到实体类的Id属性中

Mybatis的参数深入

image-20200802105157880

pojo对象就是java bean,也就是我们之前一直用的实体类

什么是ognl表达式呢?

image-20200313173738452

{}中的写法就是ognl表达式的写法

pojo的包装对象是为了使用现有的对象通过组合形成新的对象来应对更复杂的查询情况

例如我们创建了一个QueryVo对象,封装了User对象,之后在查询中如果要用到User对象的属性,就需要写user.属性名称了,因为我们的ParameterType是QueryVo:

    <select id="findUserByVo" parameterType="com.jiading.domain.QueryVo" resultMap="userMap">
        select * from user where username like #{user.username};
    </select>

javaBean属性名称和数据库列名不一样怎么办?

如果Bean中属性的名称和SQL语句中不一致怎么办,查询的时候其实好办,就把#{}里的名字改了就行,主要是封装结果的时候怎么办?

最简单的办法就是起别名,这是最简单的、行之有效的方法

image-20200313211248850

也可以在Dao的xml文件中配置属性对应关系

    <resultMap id="userMap" type="com.jiading.domain.User">
        <!--主键字段的对应,property中是Java类的属性的名称,column是myBatis的属性的名称
        -->
        <id property="userId" column="id"></id>
        <!--非主键字段的对应-->
        <result property="userName" column="username"></result>
        <result property="userAddress" column="address"></result>
        <result property="userSex" column="sex"></result>
        <result property="userBirthday" column="birthday"></result>
    </resultMap>

此外,返回值也要从resultType变成resultMap

    <!-- 根据id查询用户
    使用resultMap属性映射-->
    <select id="findById" parameterType="INT" resultMap="userMap">
        select * from user where id = #{uid}
    </select>

这样的效率会提高

Mybatis实现DAO层的开发

自己编写实现类

这个之前已经说过了,不是常用的真实情况,这里作为了解就好了

这里说一下整个过程

  1. 测试类根据xml配置创建SqlSessionFactory,并传入DAO的实现类中

        @Before
        public void init() throws IOException {
            in = Resources.getResourceAsStream("sqlMapConfig.xml");
            SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
            userDao = new UserDaoImpl(factory);
        }
    
  2. 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.jiading.dao.IUserDao">
    
        <!--配置查询所有-->
        <select id="findAll" resultType="com.jiading.domain.User">
            select * from user
        </select>
        <insert id="saveUser" parameterType="com.jiading.domain.User">
            <!-- 返回保存的表项的id
            在mysql中可以在插入完之后执行select last_insert_id();获取,这里需要写到selectKey中
            keyProperty对应的是对象的属性,keyColumn对应的是表项,order表示这里的sql什么时候执行,after表示插入之后
            -->
            <selectKey keyProperty="id" keyColumn="id" order="AFTER" resultType="java.lang.Integer">
                select last_insert_id();
            </selectKey>
            insert into user(username,address,sex,birthday)values(#{username},#{address},#{sex},#{birthday});
        </insert>
        <update id="updateUser" parameterType="com.jiading.domain.User">
            update user set username=#{username},address=#{address},sex=#{sex},birthday=#{birthday} where id=#{id};
        </update>
        <delete id="deleteUser" parameterType="java.lang.Integer">
            <!--注意对于基本类型和基本类型的包装类,如果只有一个参数时,参数的类型可以随意写-->
            delete from user where id=#{id};
        </delete>
        <!-- 根据id查询用户
        使用resultMap属性映射-->
        <select id="findById" parameterType="INT" resultType="com.jiading.domain.User">
            select * from user where id = #{uid}
        </select>
    
        <!-- 根据名称模糊查询 -->
        <select id="findByName" parameterType="string" resultType="com.jiading.domain.User">
            select * from user where username like #{name}
            <!--这里的模糊查询前后的%需要写到name中
            还有一种写法是: select * from user where username like '%${value}%',注意这里不是#{},而是${},其实这两种方法都是传递pojo对象的,作用一样
            这种写法要求这里写的必须是value。它其实是一种字符串拼接,我们之前用的是占位符。占位符相对来说更好,可以防止注入攻击,所以这种方法我们一般不用
            -->
        </select>
    
        <!-- 获取用户的总记录条数 -->
        <select id="findTotal" resultType="int">
            select count(id) from user;
        </select>
    
    </mapper>
    
  3. DAO实现类中创建SqlSession并调用其相关CRUD方法进行操作

        @Override
        public List<User> findAll() {
            SqlSession session=factory.openSession();
            List<User>users=session.selectList("com.jiading.dao.IUserDao.findAll");
            session.commit();
            session.close();
            return users;
        }
    

    相当于把框架自动化的一部分又手动实现了一遍,没太大意义

Mybatis工作原理

见这篇博文:https://www.cnblogs.com/jiading/articles/12599865.html

xml配置

properties

将一些配置信息放到properties中,xml其他地方如果使用的话就使用${name}引用即可,这样便于修改

image-20200802153948135

当然,进一步地,我们也可以把这个properties放到外部的配置文件中

image-20200802154302537

typeAlias和package

给数据类型起别名

image-20200802154608719

也可以使用package标签给整个包下的类都注册别名,此时只需要使用类名即可,一般我们把domain包给注册了,比较方便

image-20200802154742093

在mappers里也有一个package标签,它可以自动找到该包下所有dao的接口

image-20200802154918500

Mybatis连接池

实际开发中都会使用连接池,以减少获取连接所消耗的时间

Mybatis的连接池提供了3种方式的配置

配置的位置:

  • 主配置文件:SqlMapConfig.xml中的dataSource标签,type属性

    • POOLED:采用传统的Javax.DataSource规范的连接池,由Mybatis实现
    • UNPOOLED:采用传统的获取连接的方式,虽然也实现了Javax.DataSource接口,但是并没有使用连接池的思想
    • JNDI:采用服务器提供的JNDI技术来实现获取DataSource对象,不同的服务器拿到的DataSource不一样。如果不是Web或者Maven的war工程,不能使用该技术。例如,如果使用tomcat服务器,使用的就是tomcat的dbcp连接池

    Mybatis内部分别定义并实现了java.sql.DataSource接口的UnpooledDataSource和PooledDataSource来表示UNPOOLED、POOLED类型的数据源。java.sql.DataSource主要定义的就是getConnection方法,两个实现类分别用连接池和不用连接池实现了这个方法

1

idle意思是空闲的,idleConnections是一个ArrayList

全过程如下:

  1. 如果空闲连接还有,就从头部取一个出来
  2. 如果没有,但是当前连接数小于最大连接数,就创建一个新连接
  3. 如果也不行,就从activeConnections获取活动中最老的连接,如果它的使用时间超过了最大的checkoutTime,就把它从activeConnections中删除并用它的资源创建一个新的连接
  4. 如果没有超过时间,就只能先等待了

所以其实是有两个池,一个idleConnections,一个activeConnections

完整函数代码如下:

private PooledConnection popConnection(String username, String password) throws SQLException {
        boolean countedWait = false;
        PooledConnection conn = null;
        long t = System.currentTimeMillis();
        int localBadConnectionCount = 0;

        while(conn == null) {
            synchronized(this.state) {
                PoolState var10000;
                if (!this.state.idleConnections.isEmpty()) {
                    conn = (PooledConnection)this.state.idleConnections.remove(0);
                    if (log.isDebugEnabled()) {
                        log.debug("Checked out connection " + conn.getRealHashCode() + " from pool.");
                    }
                } else if (this.state.activeConnections.size() < this.poolMaximumActiveConnections) {
                    conn = new PooledConnection(this.dataSource.getConnection(), this);
                    if (log.isDebugEnabled()) {
                        log.debug("Created connection " + conn.getRealHashCode() + ".");
                    }
                } else {
                    PooledConnection oldestActiveConnection = (PooledConnection)this.state.activeConnections.get(0);
                    long longestCheckoutTime = oldestActiveConnection.getCheckoutTime();
                    if (longestCheckoutTime > (long)this.poolMaximumCheckoutTime) {
                        ++this.state.claimedOverdueConnectionCount;
                        var10000 = this.state;
                        var10000.accumulatedCheckoutTimeOfOverdueConnections += longestCheckoutTime;
                        var10000 = this.state;
                        var10000.accumulatedCheckoutTime += longestCheckoutTime;
                        this.state.activeConnections.remove(oldestActiveConnection);
                        if (!oldestActiveConnection.getRealConnection().getAutoCommit()) {
                            try {
                                oldestActiveConnection.getRealConnection().rollback();
                            } catch (SQLException var16) {
                                log.debug("Bad connection. Could not roll back");
                            }
                        }

                        conn = new PooledConnection(oldestActiveConnection.getRealConnection(), this);
                        conn.setCreatedTimestamp(oldestActiveConnection.getCreatedTimestamp());
                        conn.setLastUsedTimestamp(oldestActiveConnection.getLastUsedTimestamp());
                        oldestActiveConnection.invalidate();
                        if (log.isDebugEnabled()) {
                            log.debug("Claimed overdue connection " + conn.getRealHashCode() + ".");
                        }
                    } else {
                        try {
                            if (!countedWait) {
                                ++this.state.hadToWaitCount;
                                countedWait = true;
                            }

                            if (log.isDebugEnabled()) {
                                log.debug("Waiting as long as " + this.poolTimeToWait + " milliseconds for connection.");
                            }

                            long wt = System.currentTimeMillis();
                            this.state.wait((long)this.poolTimeToWait);
                            var10000 = this.state;
                            var10000.accumulatedWaitTime += System.currentTimeMillis() - wt;
                        } catch (InterruptedException var17) {
                            break;
                        }
                    }
                }

                if (conn != null) {
                    if (conn.isValid()) {
                        if (!conn.getRealConnection().getAutoCommit()) {
                            conn.getRealConnection().rollback();
                        }

                        conn.setConnectionTypeCode(this.assembleConnectionTypeCode(this.dataSource.getUrl(), username, password));
                        conn.setCheckoutTimestamp(System.currentTimeMillis());
                        conn.setLastUsedTimestamp(System.currentTimeMillis());
                        this.state.activeConnections.add(conn);
                        ++this.state.requestCount;
                        var10000 = this.state;
                        var10000.accumulatedRequestTime += System.currentTimeMillis() - t;
                    } else {
                        if (log.isDebugEnabled()) {
                            log.debug("A bad connection (" + conn.getRealHashCode() + ") was returned from the pool, getting another connection.");
                        }

                        ++this.state.badConnectionCount;
                        ++localBadConnectionCount;
                        conn = null;
                        if (localBadConnectionCount > this.poolMaximumIdleConnections + this.poolMaximumLocalBadConnectionTolerance) {
                            if (log.isDebugEnabled()) {
                                log.debug("PooledDataSource: Could not get a good connection to the database.");
                            }

                            throw new SQLException("PooledDataSource: Could not get a good connection to the database.");
                        }
                    }
                }
            }
        }

        if (conn == null) {
            if (log.isDebugEnabled()) {
                log.debug("PooledDataSource: Unknown severe error condition.  The connection pool returned a null connection.");
            }

            throw new SQLException("PooledDataSource: Unknown severe error condition.  The connection pool returned a null connection.");
        } else {
            return conn;
        }
    }

Mybatis的事务

MyBatis可以在sqlSession的程度实现事务管理

例如我们在测试类中:

    @Before
    public void init() throws IOException {
        in = Resources.getResourceAsStream("sqlMapConfig.xml");
        factory = new SqlSessionFactoryBuilder().build(in);
        session = factory.openSession();
        userDao = session.getMapper(IUserDao.class);
    }

    @After
    public void distroy() throws IOException {
        //提交事务,这里的事务是需要手动提交的
        session.commit();
        session.close();
        in.close();
    }

我们也可以在创建sqlSession的时候传入布尔值开启自动提交:

image-20200802163101185

当然,这个程度还是不够,因为它只是在dao层实现了事务,而我们往往是需要在service层实现,所以只靠Mybatis还是不行,我们往往是用spring的aop来实现事务的

动态SQL

if标签

注意在if中要使用and 或者or关键字,而不能使用&& 或者||

一个实例:

image-20200802163638300

可以将其看都是在字符串拼接程度上的判断,前面1=1是为了让如果后面都是false的话加的那个where关键字不是空的,然后在if中我们需要写上and 关键字来和前面的where条件拼接

where标签

where标签可以解决前面使用where 1=1这个没意义的内容

image-20200802164134735

foreach标签

foreach是为了查询的时候where xxx in的时候如何传入一个列表作为in的内容的

image-20200802165420694

{}中的变量名要和item的值对应

sql标签

sql标签可以抽取重复的sql语句,下面直接引用

image-20200802165819725

image-20200802165850167

这里要注意:

  1. 如果使用了sql标签的sql片段,就建议在后面使用其他动态SQL的标签来构造SQL,不要直接在后面拼sql文本
  2. 因为是公共的判断,所以不要在里面加分号,否则拼到其他sql中时会报错

多表查询

其实可以使用多条SQL来完成,但是如果要使用一条SQL完成,也就是使用多表查询的方式的话,就需要在实体类中进行修改了

对于一对一的情况,就是在一个实体类中封装另外一个实体类。这在ResultMapping中用association标签来表示

image-20200802204322351

例如一对多或者多对多,我们就需要在查询的实体类中设置List来存放另外一个实体类的对象,在ResultMapping中我们用Collection标签来说明

image-20200802170842281

JNDI数据源

JNDI(java Naming and Directory Interface),是sun推出的一套规范,属于JAVAEE的技术之一,目的是模仿Windows中的注册表在服务器中注册数据源

image-20200802204837956

使用流程

1.1 创建Maven的war工程并导入坐标

从archtype(骨架)创建,选择maven-archtype-webapp进行创建

img

<dependencies>
    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis</artifactId>
      <version>3.4.5</version>
    </dependency>

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

    <dependency>
      <groupId>log4j</groupId>
      <artifactId>log4j</artifactId>
      <version>1.2.12</version>
    </dependency>

    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.10</version>
    </dependency>

  <dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>servlet-api</artifactId>
    <version>2.5</version>
  </dependency>

  <dependency>
    <groupId>javax.servlet.jsp</groupId>
    <artifactId>jsp-api</artifactId>
    <version>2.0</version>
  </dependency>
</dependencies>

1.2 在webapp文件下创建META-INF目录

img

1.3 在META-INF目录中建立一个名为context.xml的配置文件

img

<?xml version="1.0" encoding="UTF-8"?>
 <Context>
 <!-- 
 <Resource 
 name="jdbc/eesy_mybatis"         数据源的名称,可以自己指定
 type="javax.sql.DataSource"          数据源类型
 auth="Container"            数据源提供者,这里写的是由容器提供,例如tomcat
 maxActive="20"             最大活动数
 maxWait="10000"              最大等待时间
 maxIdle="5"                最大空闲数
 username="root"              用户名
 password="1234"              密码
 driverClassName="com.mysql.jdbc.Driver"     驱动类
 url="jdbc:mysql://localhost:3306/eesy_mybatis" 连接url字符串
 />
 -->
 <Resource 
 name="jdbc/eesy_mybatis"
 type="javax.sql.DataSource"
 auth="Container"
 maxActive="20"
 maxWait="10000"
 maxIdle="5"
 username="root"
 password="1234"
 driverClassName="com.mysql.jdbc.Driver"
 url="jdbc:mysql://localhost:3306/eesy_mybatis"
 />
 </Context>

1.4 修改SqlMapConfig.xml中的配置

img

java:comp/env/是一个固定路径,后面的jdbc/eesy_mybatis是自己的名称

这里要注意,JNDI是依赖于服务器的,所以必须启动服务器,例如tomcat才能获取到数据源,所以必须通过tomcat进行测试,而不能直接使用测试类

Mybatis延迟加载与缓存

image-20200802210442345

对多的一般延迟加载,对一的一般立即加载

延迟加载

实现延迟加载的步骤如下:

  1. 修改xml文件,在查询方法中只写查询外部对象的sql,内部对象的查询方法写在association的select标签中,查询的属性写在column标签中

    <mapper namespace="com.itheima.dao.IAccountDao">
    <!-- 建立对应关系 -->
    <resultMap type="account" id="accountMap">
    <id column="aid" property="id"/>
    <result column="uid" property="uid"/>
    <result column="money" property="money"/>
    <!-- 它是用于指定从表方的引用实体属性的 -->
    <association property="user" javaType="user"
    select="com.itheima.dao.IUserDao.findById"
    column="uid">
    </association>
    </resultMap>
    <select id="findAll" resultMap="accountMap">
    select * from account
    </select>
    </mapper>
    

    注意select的值是一个方法,我们需要准备这样一个方法

  2. 在全局的xml文件中开启延迟加载

    image-20200802211846688

    <settings>
    <setting name="lazyLoadingEnabled" value="true"/>
    <setting name="aggressiveLazyLoading" value="false"/>
    </settings>
    

    此时,如果只get外部对象的信息,内部对象的信息就不会被查询到

    对于一对多和多对多关系,使用collections进行配置也是一样的道理

缓存

什么是缓存

image-20200802212354442

缓存终究会存在一个数据同步问题,这是怎么也没办法避免的(参考redis),所以如果对数据的正确性非常在意的话就不要在这个数据上使用缓存

Mybatis的一级缓存

image-20200802212454412

由此可知,一级缓存是依赖于sqlSession的,所以如果这个session被关闭了,缓存的结果就没有了

一级缓存的例子:

image-20200802212825729

连续查两次的话,其实只会执行一次,然后返回的对象也是同一个(不是指值相同,而是完全地同一个对象)

image-20200802213053920

清空一下,就没有了

一级缓存的同步

image-20200802213213574

所以对于同一个session是同步安全的,有修改就会清空缓存,但是多线程下却不能保证

二级缓存

image-20200802213344239

二级缓存不是默认开启的,需要多个步骤进行开启(很明显,二级缓存要保存的数据量很大,同步问题也更突出,所以不是默认打开的)

image-20200802213559590

测试二级缓存

  1. SqlMapConfig.xml

    image-20200802213716935

    这一步可以省略,因为默认就是true

  2. IUserDao.xml

    image-20200802213816282

    整体上以及方法上都要设置

  3. 执行

image-20200802213458862

注意最后对象的==结果是false,因为二级缓存缓存的是数据而不是对象,每次对象都是新建的

注解开发

注解开发省略的是IUserDao.xml文件,全局的SqlMapConfig.xml不能省略

SqlMapConfig.xml中的mappers下是package标签,里面指定的是接口对象所在的包

注意注解开发和xml开发是不能并存的,也就是如果用注解的话,resources下就不能再有我们之前用的相同路径下的xml文件了,否则不管全局xml文件的mappers如何设置,一定会报错

注解实现ResultMapping

image-20200802220300987

复用resultMap:

image-20200802220409161

注解实现多表查询

image-20200802220655861

查询一个对象时

image-20200802220743839

查询多个对象时

注解开启二级缓存

一级缓存不用开启

在全局配置文件中的开启和上面一样

在Dao接口中开启:

image-20200802221006602

注意注解中只需要在整个接口上开启即可,该接口的所有方法都会开启二级缓存

传参处理

具体可以看这篇文章:https://www.cnblogs.com/jiading/articles/13423428.html

总结一下,对于传入单个参数时,无论是基本类型还是复杂类型,都可以不适用@Param注解,直接写参数名即可,最好我们在#{}写的参数名和方法中的参数名一样(对于基本类型),但是其实即使不一样也不会出错;但是如果是多个参数的话,要么用Index(仅指基本类型),要么用@Param给他们确定一个名称

posted @ 2020-08-02 22:13  别再闹了  阅读(150)  评论(0)    收藏  举报