Begin

前身iBatis,由Apache->Google

  • MyBatis可以简化JDBC操作,实现数据的持久化。

ORM

  • 对象-关系映射(Object/Relation Mapping,简称ORM),是随着面向对象的软件开发方法发展而产生的。面向对象的开发方法是当今企业级应用开发环境中的主流开发方法,关系数据库是企业级应用环境中永久存放数据的主流数据存储系统。对象和关系数据是业务实体的两种表现形式,业务实体在内存中表现为对象,在数据库中表现为关系数据。内存中的对象之间存在关联和继承关系,而在数据库中,关系数据无法直接表达多对多关联和继承关系。因此,对象-关系映射(ORM)系统一般以中间件的形式存在,主要实现程序对象到关系数据库数据的映射。
  • MyBatis是ORM的一个实现,持久层框架。相关还有Hibernate, Spring Data JPA等
  • ORM使得的开发人员像操作对象一样操作数据库表。
  • MyBatis是一个优秀的基于java的持久层框架,它内部封装了jdbc,使开发者只需要关注SQL语句本身,而不需要花费精力去处理加载驱动、创建连接、创建statement等繁杂的过程。 MyBatis通过xml或注解的方式将要执行的各种statement配置起来,并通过java对象和statement中SQL的动态参数进行映射生成最终执行的SQL语句,最后由MyBatis框架执行SQL并将结果映射为java对象并返回。 采用ORM思想解决了实体和数据库映射的问题,对jdbc进行了封装,屏蔽了jdbc api底层访问细节,使我们不用与jdbc api打交道,就可以完成对数据库的持久化操作。

MyBatis入门

数据库准备

DROP TABLE IF EXISTS `user`;

CREATE TABLE `user` (
  `id` int(11) NOT NULL auto_increment,
  `username` varchar(32) NOT NULL COMMENT '用户名称',
  `birthday` datetime default NULL COMMENT '生日',
  `sex` char(1) default NULL COMMENT '性别',
  `address` varchar(256) default NULL COMMENT '地址',
  PRIMARY KEY  (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;



insert  into `user`(`id`,`username`,`birthday`,`sex`,`address`) values (41,'老王','2018-02-27 17:47:08','男','北京'),(42,'小二王','2018-03-02 15:09:37','女','北京金燕龙'),(43,'小二王','2018-03-04 11:34:34','女','北京金燕龙'),(45,'传智播客','2018-03-04 12:04:06','男','北京金燕龙'),(46,'老王','2018-03-07 17:37:26','男','北京'),(48,'小马宝莉','2018-03-08 11:44:00','女','北京修正');

创建maven工程并导入坐标

        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.4</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.49</version>
        </dependency>
    </dependencies>

创建实体类和dao的接口

实体类:

package com.moluuser;

import java.io.Serializable;
import java.util.Date;

public class User implements Serializable {
    // 和数据库命名保持一致
    private Integer id;
    private String username;
    private Date birthday;
    private String sex;
    private String address;

    @Override
    public String toString() {
        return "user{" +
                "id=" + id +
                ", username='" + username + '\'' +
                ", birthday=" + birthday +
                ", sex='" + sex + '\'' +
                ", address='" + address + '\'' +
                '}';
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public Date getBirthday() {
        return birthday;
    }

    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }
}
package com.moluuser.dao;

import com.moluuser.User;

import java.util.List;

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

创建MyBatis的主配置文件

resource下创建 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://moluuser.cn:3306/user"/>
                <property name="username" value="root"/>
                <property name="password" value="password"/>
            </dataSource>
        </environment>
    </environments>
<!--    指定映射配置文件的位置,指的是每个dao下独立的配置文件-->
    <mappers>
        <mapper resource="com.moluuser/dao/IUserDao.xml"></mapper>
    </mappers>
</configuration>

创建映射配置文件

resources\com.moluuser\dao下创建 IUserDao.xml

  • MyBatis的映射配置文件位置必须和dao接口的包结构相同
  • 映射配置文件的mapper标签namespace属性的取值必须是dao接口的全限定类名
  • 映射配置文件的操作配置(select),id属性的取值必须是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">
<mapper namespace="com.moluuser.dao.IUserDao">
<!--    配置查询所有-->
    <select id="findAll" resultType="com.moluuser.User">
        SELECT * from user
    </select>
</mapper>

创建测试类

package com.moluuser.test;

import com.moluuser.User;
import com.moluuser.dao.IUserDao;
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 {
//        读取配置文件
        InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml");
//        创建SqlSessionFactory工厂
        SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
        SqlSessionFactory factory = builder.build(in);
//        使用工厂生产SqlSession对象
        SqlSession session = factory.openSession();
//        使用SqlSession创建Dao接口的代理对象
        IUserDao userDao = session.getMapper(IUserDao.class);
//        使用代理对象执行方法
        List<User> users = userDao.findAll();
        for (User user: users) {
            System.out.println(user);
        }
//        释放资源
        session.close();
        in.close();
    }
}

运行

目录结构

得到如下结果

  • 我们在实际开发中,都是越简便越好,所以都是采用不写dao实现类的方式。不管使用XML还是注解配置。但是MyBatis它是支持写dao实现类的。

设计模式分析

  1. 读取配置文件

    • ServletContext对象的 getRealPath()获取路径
  2. 创建SqlSessionFactory工厂

    • 建造者模式(Builder Pattern)使用多个简单的对象一步一步构建成一个复杂的对象。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
  3. 使用工厂生产SqlSession

    • 不需要使用 new
    • 解耦,降低类之间的依赖关系
  4. 使用SqlSession创建Dao接口的代理对象

    • 代理模式:在代理模式(Proxy Pattern)中,一个类代表另一个类的功能。这种类型的设计模式属于结构型模式。在代理模式中,我们创建具有现有对象的对象,以便向外界提供功能接口。
  5. 执行dao中的方法
  6. 释放资源

CRUD

  • 增加(Create)、读取(Retrieve)、更新(Update)和删除(Delete)

测试类MyBatisTest.class

package com.moluuser.test;

import com.moluuser.User;
import com.moluuser.dao.IUserDao;
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 org.junit.After;
import org.junit.Before;
import org.junit.Test;

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

public class MyBatisTest {
    private InputStream in = null;
    private SqlSession session = null;
    private IUserDao userDao;

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

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

    @Test
    public void testFindAll() throws IOException {
        List<User> users = userDao.findAll();
        for (User user : users) {
            System.out.println(user);
        }
    }

    //    增加
    @Test
    public void testSave() {
        User user = new User();
        user.setAddress("Mars");
        user.setUsername("Alpha");
        user.setSex("男");
        user.setBirthday(new Date());
        userDao.saveUser(user);
//        提交事务
        session.commit();
    }

    //    更新
    @Test
    public void testUpdate() {
        User user = new User();
        user.setAddress("Mars");
        user.setUsername("Alpha");
        user.setSex("男");
        user.setId(48);
        user.setBirthday(new Date());
        userDao.updateUser(user);
        session.commit();
    }

    @Test
    public void testDelete() {
        userDao.deleteUser(46);
        session.commit();
    }

    @Test
    public void testFindById() {
        User user = userDao.findById(51);
        System.out.println(user.toString());
    }

    @Test
    public void findByName() {
        List<User> users = userDao.findByName("%l%");
        for (User user : users) {
            System.out.println(user.toString());
        }

    }
}

DAO接口IUserDao

package com.moluuser.dao;

import com.moluuser.User;

import java.util.List;

// 用户的持久层操作
public interface IUserDao {
    List<User> findAll();
    void saveUser(User user);
    void updateUser(User user);
    void deleteUser(Integer uid);
    User findById(Integer id);
    List<User> findByName(String s);
}

IUserDao.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.moluuser.dao.IUserDao">
    <!--    配置查询所有-->
    <select id="findAll" resultType="com.moluuser.User">
        SELECT * from user
    </select>
    <!--    保存用户-->
    <insert id="saveUser" parameterType="com.moluuser.User">
        insert into user(username, address, sex, birthday) values(#{username}, #{address}, #{sex}, #{birthday})
    </insert>
    <!--    更新用户-->
    <update id="updateUser" parameterType="com.moluuser.User">
        update user set username=#{username}, address=#{address}, sex=#{sex}, birthday=#{birthday} where id = #{id}
    </update>
    <!--    删除用户-->
    <!--    parameterType可以写Integer, INT, java.long.Integer都可以-->
    <delete id="deleteUser" parameterType="Integer">
-- 有一个参数时,{uid}中的参数随便写,只是占位符
        delete from user where id=#{uid}
    </delete>
    <!--    根据id查询用户-->
    <select id="findById" parameterType="INT" resultType="com.moluuser.User">
        select * from user where id = #{id}
    </select>
    <!--    根据名称模糊查询-->
    <select id="findByName" parameterType="string" resultType="com.moluuser.User">
        select * from user where username like #{name}
--         select * from user where username like '%${value}%'
-- 这个是使用statement实现的,不推荐
    </select>
</mapper>
  • 新增用户后,同时还要返回当前新增用户的id值,因为id是由数据库的自动增长来实现的,所以就相当于我们要在新增后将自动增长auto_increment的值返回。
    <insert id="saveUser" parameterType="USER"> <!-- 配置保存时获取插入的id -->
        <selectKey keyColumn="id" keyProperty="id" resultType="int">
        select last_insert_id();
        </selectKey> 
         insert into user(username,birthday,sex,address) values(#{username},#{birthday},#{sex},#{address})
    </insert>

执行SQL后,user的id属性就会被赋值

MyBatis的参数深入

parameterType配置参数

基本类型和String我们可以直接写类型名称,也可以使用包名.类名的方式,例如:java.lang.String。 实体类类型,目前我们只能使用全限定类名。 究其原因,是mybaits在加载时已经把常用的数据类型注册了别名,从而我们在使用时可以不写包名,而我们的是实体类并没有注册别名,所以必须写全限定类名。在今天课程的最后一个章节中将讲解如何注册实体类的别名。

在MyBatis的官方文档的说明

OGNL表达式

  • 它是一门表达式语言,除了用来设置和获取Java对象的属性之外,另外提供诸如集合的投影和过滤以及lambda表达式等
ObjectGraphicNavigationLanguage
对象导航语言

使用实体类的包装对象作为查询条件

查询的实体类

package com.moluuser;

public class QueryVo {
    public User getUser() {
        return user;
    }

    public void setUser(User user) {
        this.user = user;
    }

    private User user;

}

IUserDao.xml

<!--    根据QueryVo查询用户-->
    <select id="findUserByVo" parameterType="com.moluuser.QueryVo" resultType="com.moluuser.User">
        select * from user where username like #{user.username}
    </select>

测试类

//    使用QueryVo作为查询条件
    @Test
    public void testFindByVo() {
        QueryVo vo = new QueryVo();
        User user = new User();
        user.setUsername("%l%");
        vo.setUser(user);
        List<User> users = userDao.findUserByVo(vo);
        for (User us: users) {
            System.out.println(us);
        }
    }

解决实体类属性和数据库列名不对应的两种方式

  1. SQL语句起别名(效率高)
  2. 配置 IUserDao.xml

MyBatis传统DAO层开发

  • 使用MyBatis开发Dao,通常有两个方法,即原始Dao开发方式和Mapper接口代理开发方式。而现在主流的开发方式是接口代理开发方式,这种方式总体上更加简便。
  • 仅为了解内容

持久层Dao接口

public interface IUserDao {
    /**
     * 查询所有用户 * @return
     */
    List<User> findAll();

    /**
     * 根据id查询 * @param userId * @return
     */
    User findById(Integer userId);

    /**
     * 保存用户 * @param user * @return 影响数据库记录的行数
     */
    int saveUser(User user);

    /**
     * 更新用户 * @param user* @return 影响数据库记录的行数
     */
    int updateUser(User user);

    /**
     * 根据id删除用户 * @param userId * @return
     */
    int deleteUser(Integer userId);

    /**
     * 查询总记录条数 * @return
     */
    int findTotal();
}

持久层Dao实现类

public class UserDaoImpl implements IUserDao {
    private SqlSessionFactory factory;

    public UserDaoImpl(SqlSessionFactory factory) {
        this.factory = factory;
    }

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

    @Overridepublic
    User findById(Integer userId) {
        SqlSession session = factory.openSession();
        User user = session.selectOne("com.itheima.dao.IUserDao.findById", userId);
        session.close();
        return user;
    }

    @Override
    public int saveUser(User user) {
        SqlSession session = factory.openSession();
        int res = session.insert("com.itheima.dao.IUserDao.saveUser", user);
        session.commit();
        session.close();
        return res;
    }

    @Override
    public int updateUser(User user) {
        SqlSession session = factory.openSession();
        int res = session.update("com.itheima.dao.IUserDao.updateUser", user);
        session.commit();
        session.close();
        return res;
    }

    @Override
    public int deleteUser(Integer userId) {
        SqlSession session = factory.openSession();
        int res = session.delete("com.itheima.dao.IUserDao.deleteUser", userId);
        session.commit();
        session.close();
        return res;
    }

    @Override
    public int findTotal() {
        SqlSession session = factory.openSession();
        int res = session.selectOne("com.itheima.dao.IUserDao.findTotal");
        session.close();
        return res;
    }
}

持久层映射配置

测试类

SqlMapConfig.xml配置文件

SqlMapConfig.xml中配置的内容和顺序

properties(属性)

  • 在使用properties标签配置时,我们可以采用两种方式指定属性配置。

第一种

<properties>
    <property name="driver" value="com.mysql.jdbc.Driver"/>
    <property name="url" value="jdbc:mysql://moluuser.cn:3306/user?useUnicode=true&characterEncoding=UTF-8"/>
    <property name="username" value="root"/>
    <property name="password" value="password"/>
</properties>

第二种

在classpath下定义db.properties文件
jdbc.driver=com.mysql.jdbc.Driver 
jdbc.url=jdbc:mysql://localhost:3306/eesy 
jdbc.username=root 
jdbc.password=1234
properties标签配置

此时我们的dataSource标签就变成了引用上面的配置
<dataSource type="POOLED">
    <property name="driver" value="${jdbc.driver}"/>
    <property name="url" value="${jdbc.url}"/>
    <property name="username" value="${jdbc.username}"/>
    <property name="password" value="${jdbc.password}"/>
</dataSource>

typeAliases(类型别名)

  • 自定义别名
<typeAliases> 
    <!-- 单个别名定义,不区分大小写 -->
    <typeAlias alias="user" type="com.itheima.domain.User"/> 
    <!-- 批量别名定义,扫描整个包下的实体类,别名为类名(不再区分大小写) -->
    <package name="com.itheima.domain"/>
    <package name="其它包"/>
</typeAliases>

mappers(映射器)

<mapper resource=" " />

使用相对于类路径的资源 如:<mapper resource="com/itheima/dao/IUserDao.xml" />

<mapper class=" " />

使用mapper接口类路径
如:<mapper class="com.itheima.dao.UserDao"/>
注意:此种方法要求mapper接口名称和mapper映射文件名称相同,且放在同一个目录中。

<package name=""/>

注册指定包下的所有mapper接口
如:<package name="cn.itcast.mybatis.mapper"/>
注意:此种方法要求mapper接口名称和mapper映射文件名称相同,且放在同一个目录中。

连接池

  • 在MyBatis的SqlMapConfig.xml配置文件中,通过 <dataSource type=”pooled”>来实现MyBatis中连接池的配置。

在MyBatis中我们将它的数据源dataSource分为以下几类:

type描述
POOLED采用传统的 javax.sql.DataSource规范中的连接池,MyBatis中有针对规范的实现
UNPOOLED采用传统的获取连接的方式,虽然也实现 Javax.sql.DataSource接口,但是并没有使用池的思想。
JNDI采用服务器提供的JNDI技术实现,来获取 DataSource对象,不同的服务器所能拿到 DataSource是不一样。注意:如果不是web或者maven的war工程,是不能使用的。我们使用的是tomcat服务器,采用连接池就是dbcp连接池。
<!--            配置数据源/连接池-->
<dataSource type="POOLED">
    <!--                配置连接数据库的基本信息-->
    <property name="driver" value="com.mysql.jdbc.Driver"/>
    <property name="url"
                value="jdbc:mysql://moluuser.cn:3306/user?useUnicode=true&characterEncoding=UTF-8"/>
    <property name="username" value="root"/>
    <property name="password" value="password"/>
</dataSource>

MyBatis中的事务

事务的定义

事务(Transaction)是并发控制的基本单位。所谓的事务,它是一个操作序列,这些操作要么都执行,要么都不执行,它是一个不可分割的工作单位。

事务的四大特性ACID

  • Atomic(原子性):事务中包含的操作被看做一个逻辑单元,这个逻辑单元中的操作要么全部成功,要么全部失败。
  • Consistency(一致性):只有合法的数据可以被写入数据库,否则事务应该将其回滚到最初状态。
  • Isolation(隔离性):事务允许多个用户对同一个数据进行并发访问,而不破坏数据的正确性和完整性。同时,并行事务的修改必须与其他并行事务的修改相互独立。
  • Durability(持久性):事务结束后,事务处理的结果必须能够得到固化。

不考虑隔离性会产生的3个问题

脏读

脏读是指在一个事务处理过程里读取了另一个未提交的事务中的数据。

当一个事务正在多次修改某个数据,而在这个事务中这多次的修改都还未提交,这时一个并发的事务来访问该数据,就会造成两个事务得到的数据不一致。

例如:用户A向用户B转账100元,对应SQL命令如下

    update account set money=money+100 where name=’B’;  (此时A通知B)

    update account set money=money - 100 where name=’A’;

当只执行第一条SQL时,A通知B查看账户,B发现确实钱已到账(此时即发生了脏读),而之后无论第二条SQL是否执行,只要该事务不提交,则所有操作都将回滚,那么当B以后再次查看账户时就会发现钱其实并没有转。

不可重复读

不可重复读是指在对于数据库中的某个数据,一个事务范围内多次查询却返回了不同的数据值,这是由于在查询间隔,被另一个事务修改并提交了。

例如事务T1在读取某一数据,而事务T2立马修改了这个数据并且提交事务给数据库,事务T1再次读取该数据就得到了不同的结果,发送了不可重复读。

不可重复读和脏读的区别是,脏读是某一事务读取了另一个事务未提交的脏数据,而不可重复读则是读取了前一事务提交的数据。

虚读(幻读)

幻读是事务非独立执行时发生的一种现象。例如事务T1对一个表中所有的行的某个数据项做了从“1”修改为“2”的操作,这时事务T2又对这个表中插入了一行数据项,而这个数据项的数值还是为“1”并且提交给数据库。而操作事务T1的用户如果再查看刚刚修改的数据,会发现还有一行没有修改,其实这行是从事务T2中添加的,就好像产生幻觉一样,这就是发生了幻读。

幻读和不可重复读都是读取了另一条已经提交的事务(这点就脏读不同),所不同的是不可重复读查询的都是同一个数据项,而幻读针对的是一批数据整体(比如数据的个数)。

解决办法:四种隔离级别

  1. Serializable (串行化):可避免脏读、不可重复读、幻读的发生。
  2. Repeatable read (可重复读):可避免脏读、不可重复读的发生。
  3. Read committed (读已提交):可避免脏读的发生。
  4. Read uncommitted (读未提交):最低级别,任何情况都无法保证。

MyBatis自动提交事务的设置

通过sqlsession对象的commit方法和rollback方法实现事务的提交和回滚

MyBatis的动态SQL语句

if标签

  • 我们根据实体类的不同取值,使用不同的SQL语句来进行查询。比如在id如果不为空时可以根据id查询,如果username不为空时还要加入用户名作为条件。这种情况在我们的多条件组合查询中经常会碰到。

持久层Dao接口

/** 
 * 根据用户信息,查询用户列表 * 
 * @param user * 
 * @return 
 * */ 
List<User> findByUser(User user);

持久层Dao映射配置

测试

    @Test
    public void testFindByUser() {
        User u = new User();
        u.setUsername("%name%");
        u.setAddress("%address%");
        // 执行操作 
        List<User> users = userDao.findByUser(u);
        for (User user : users) {
            System.out.println(user);
        }
    }

where标签

  • 为了简化上面where 1=1的条件拼装,我们可以采用 <where>标签来简化开发。

持久层Dao映射配置

forecho标签

需求

传入多个id查询用户信息,用下边两个SQL实现:

SELECT * FROM USERS WHERE username LIKE '%张%' AND (id =10 OR id =89 OR id=16) 
SELECT * FROM USERS WHERE username LIKE '%张%' AND id IN (10,89,16) 

这样我们在进行范围查询时,就要将一个集合中的值,作为参数动态添加进来。

在QueryVo中加入一个List集合用于封装参数

public class QueryVo implements Serializable { 
    private List<Integer> ids;
    public List<Integer> getIds() { 
        return ids; 
    } 
    public void setIds(List<Integer> ids) { 
        this.ids = ids; 
    } 
}

持久层Dao接口

    List<User> findInIds(QueryVo vo);

持久层Dao映射配置

测试方法

MyBatis中简化编写的SQL片段

  • SQL中可将重复的SQL提取出来,使用时用include引用即可,最终达到SQL重用的目的。

定义代码片段

<!-- 抽取重复的语句代码片段 --> 
<sql id="defaultSql"> 
select * from user 
</sql>

引用代码片段

        <!-- 配置查询所有操作 -->
<select id="findAll" resultType="user">
    <include refid="defaultSql"></include>
</select>
        <!-- 根据id查询 -->
<select id="findById" resultType="UsEr" parameterType="int">
    <include refid="defaultSql"></include>
        where id = #{uid}
</select>

MyBatis多表查询

一对一查询

方法一

  • 使用子类

  • 为了能够封装上面SQL语句的查询结果,定义 AccountCustomer类中要包含账户信息同时还要包含用户信息,所以我们要在定义AccountUser类时可以继承User类。

方法二

  • 定义实体类关系
  1. 在Account类中加入User类的对象作为Account类的一个属性。
  2. 重新定义 AccountDao.xml文件

一对多查询

  1. SQL
  2. User类加入List<Account>
  3. 用户持久层Dao映射文件配置

多对多

延迟加载策略

  • 延迟加载:就是在需要用到数据时才进行加载,不需要用到数据时就不加载数据。延迟加载也称按需加载,懒加载.
  • 好处:先从单表查询,需要时再从关联表去关联查询,大大提高数据库性能,因为查询单表要比关联查询多张表速度要快。
  • 坏处:因为只有当需要用到数据时,才会进行数据库查询,这样在大批量数据查询时,因为查询工作也要消耗时间,所以可能造成用户等待时间变长,造成用户体验下降。

使用assocation实现延迟加载

使用collection实现延迟加载

MyBatis缓存

  • 什么是缓存:存在于内存中的临时数据。
  • 为什么使用缓存:减少和数据库的交互次数,提高执行效率。
  • 什么样的数据能使用缓存,什么样的数据不能使用

    • 适用于缓存:
      经常查询并且不经常改变的。

    数据的正确与否对最终结果影响不大的。

    • 不适用于缓存:
      经常改变的数据

    数据的正确与否对最终结果影响很大的。
    例如:商品的库存,银行的汇率,股市的牌价。

MyBatis中的一级缓存和二级缓存

一级缓存:

它指的是MyBatis中SqlSession对象的缓存。
当我们执行查询之后,查询的结果会同时存入到SqlSession为我们提供一块区域中。
该区域的结构是一个Map。当我们再次查询同样的数据,MyBatis会先去sqlsession中
查询是否有,有的话直接拿出来用。
当SqlSession对象消失时,MyBatis的一级缓存也就消失了。
当调用SqlSession的修改,添加,删除,commit(),close()等方法时,就会清空一级缓存。

二级缓存:

它指的是MyBatis中SqlSessionFactory对象的缓存。由同一个SqlSessionFactory对象创建的SqlSession共享其缓存。
二级缓存的使用步骤:
    第一步:让MyBatis框架支持二级缓存(在SqlMapConfig.xml中配置)
    第二步:让当前的映射文件支持二级缓存(在IUserDao.xml中配置)
    第三步:让当前的操作支持二级缓存(在select标签中配置)

二级缓存的开启与关闭

第一步:在SqlMapConfig.xml文件开启二级缓存

<settings> 
<!-- 开启二级缓存的支持 --> 
<setting name="cacheEnabled" value="true"/> 
</settings>
  • 因为cacheEnabled的取值默认就为true,所以这一步可以省略不配置。为true代表开启二级缓存;为false代表不开启二级缓存。

第二步:配置相关的Mapper映射文件

  • <cache>标签表示当前这个mapper映射将使用二级缓存,区分的标准就看mapper的namespace值。
    <?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.itheima.dao.IUserDao"> 
        <!-- 开启二级缓存的支持 -->
        <cache></cache>
    </mapper>

第三步:配置statement上面的useCache属性

<!-- 根据id查询 -->
<select id="findById" resultType="user" parameterType="int" useCache="true">
    select * from user where id = #{uid}
</select>
  • 将UserDao.xml映射文件中的 <select>标签中设置useCache="true"代表当前这个statement要使用二级缓存,如果不使用二级缓存可以设置为false。
  • 注意:针对每次查询都需要最新的数据sql,要设置成useCache=false,禁用二级缓存。
  • 当我们在使用二级缓存时,所缓存的类一定要实现java.io.Serializable接口,这种就可以使用序列化方式来保存对象。

注解开发

常用注解

@Insert:实现新增 
@Update:实现更新 
@Delete:实现删除 
@Select:实现查询 
@Result:实现结果集封装 
@Results:可以与@Result一起使用,封装多个结果集 
@ResultMap:实现引用
@Results定义的封装 
@One:实现一对一结果集封装 
@Many:实现一对多结果集封装 
@SelectProvider:实现动态SQL映射 
@CacheNamespace:实现注解二级缓存的使用

单表CRUD操作(代理Dao方式)

不需要 IUserDao.xml直接写 IUserDao.class

package com.moluuser.dao;

import com.moluuser.User;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Select;

import java.util.List;

public interface IUserDao {
    @Select("select * from user")
    List<User> findAll();
    @Insert("insert into user(username, address, sex, birthday) values(#{username}, #{address}, #{sex}, #{birthday})")
    void saveUser(User user);
}

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">
<configuration>
<!--    引入外部配置文件-->
    <properties resource="jdbcConfig.properties"></properties>
<!--    配置别名-->
    <typeAliases>
        <package name="com.moluuser"></package>
    </typeAliases>
<!--    配置环境-->
    <environments default="mysql">
        <environment id="mysql">
            <transactionManager type="JDBC"></transactionManager>
            <dataSource type="POOLED">
                <property name="driver" value="${jdbc.driver}"/>
                <property name="url" value="${jdbc.url}"/>
                <property name="username" value="${jdbc.username}"/>
                <property name="password" value="${jdbc.password}"/>
            </dataSource>
        </environment>
    </environments>
<!--    指定带有注解的dao接口所在位置-->
    <mappers>
        <package name="com.moluuser.dao"/>
    </mappers>
</configuration>

jdbcConfig.properties

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://moluuser.cn:3306/user?useUnicode=true&characterEncoding=UTF-8
jdbc.username=root
jdbc.password=password

多表查询操作

  • 实现复杂关系映射之前我们可以在映射文件中通过配置 <resultMap>来实现,在使用注解开发时我们需要借助@Results注解,@Result注解,@One注解,@Many注解。

缓存的配置

在SqlMapConfig中开启二级缓存支持

<!-- 配置二级缓存 --> 
<settings> 
<!-- 开启二级缓存的支持 -->
<setting name="cacheEnabled" value="true"/>
</settings>

在持久层接口中使用注解配置二级缓存

@CacheNamespace(blocking=true)//mybatis基于注解方式实现配置二级缓存
public interface IUserDao {}

参考链接:




扫一扫在手机打开当前页