Mybatis入门

这是学习颜群老师的mybatis教程后整理的笔记

Ⅰ、mybatis介绍与使用

1.mybatis是什么

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

  • ORM的概念:Object Relational Mapping对象关系映射,例如person表对应一个person对象

    Mybatis是ORM的一个实现。有了mybatis,开发人员像操作对象一样操作数据库表

2.怎么用mybatis

  • 用maven

    <dependency>
     <groupId>org.mybatis</groupId>
     <artifactId>mybatis</artifactId>
     <version>x.x.x</version>
    </dependency>
    
  • 导入jar包,去官网下载(mybatis-x.x.x.jar)

3.第一次用mybatis操作数据库

构建路径/源路径:java代码在的路径

  • conf.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>
        <!--运行的环境-->
       <environments default="development">
             <!--设置环境的id-->
           <environment id="development">
           <transactionManager type="JDBC"/> 
              <!--配置数据源类型-->
               <dataSource type="POOLED">
               <!-- 配置数据库信息 -->
               <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
               <property name="url" value="jdbc:mysql://localhost:3306/mysqldemo?serverTimezone=UTC"/>
               <property name="username" value="root"/>
               <property name="password" value="123456"/>
           </dataSource>
           </environment>
       </environments>
       <mappers>
          <!-- 加载数据库映射文件 -->
          <mapper resource="com/whl/mapper/personMapper.xml"/>
       </mappers>
    </configuration>
    
    • dataSource标签的type属性:配置数据源类型
      • UNPOOLED:传统的JDBC模式(每次访问数据库、均需要打开、关闭数据库连接)
      • POOLED:使用数据库连接池
      • JNDI:从tomcat中获取一个内置的数据库连接池(数据库连接池--数据源)
    • environment标签的id属性:设置这个运行环境的id(测试、运行...)
    • environments标签的default的属性:填写当前运行需要的环境id值,与environment标签的id属性相对应
    • mapper标签的resource属性:数据库映射文件mapper.xml的相对路径
    • property标签:用于配置数据库连接字符串、用户名、密码、驱动等信息
    • transactionManager标签:用于设置事务方式
      • JDBC
      • MANAGER
  • personMapper.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.whl.entity.PersonMapper">
      <select id="queryPersonById" resultType="com.whl.entity.Person" parameterType="int">
          select * from person where id = #{id}
      </select>
      
      <insert id="addPerson" parameterType="com.whl.entity.Person">
          insert into person(id,name,age) values(#{id},#{name},#{age})
      </insert>
      
      <delete id="deletePersonById" parameterType="int">
          delete from person where id = #{id}
      </delete>
      
      <update id="updatePersonById" parameterType="com.whl.entity.Person">
          update person set name=#{name},age=#{age} where id=#{id}
      </update>
      
      <select id="queryAllPerson" resultType="com.whl.entity.Person">
          select * from person
      </select>
    </mapper>
    
    • mapper标签的namespace属性:放映射文件的路径,不用xml后缀

    • SQL语句类型的标识符

      • select
      • insert
      • delete
      • update

      以上四种标签中的id值来标识唯一一条SQL语句

  • 实体类Person

    package com.whl.entity;
    
    public class Person {
      private int id;
      private String name;
      private int age;
      
      public Person() {
    
      }
      public Person(int id, String name, int age) {
          this.id = id;
          this.name = name;
          this.age = age;
      }
      public int getId() {
          return id;
      }
      public void setId(int id) {
          this.id = id;
      }
      public String getName() {
          return name;
      }
      public void setName(String name) {
          this.name = name;
      }
      public int getAge() {
          return age;
      }
      public void setAge(int age) {
          this.age = age;
      }
      @Override
      public String toString() {
          return "Person [id=" + id + ", name=" + name + ", age=" + age + "]";
      }
      
    }
    
  • mysql中创建person表如下

    -------------------------------------+
    | person | CREATE TABLE `person` (
      `id` int DEFAULT NULL,
      `name` varchar(20) DEFAULT NULL,
      `age` int DEFAULT NULL
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8 |
    
  • 测试类

    package com.whl.entity;
    
    import java.io.IOException;
    import java.io.Reader;
    
    import org.apache.ibatis.io.Resources;
    import org.apache.ibatis.session.SqlSession;
    import org.apache.ibatis.session.SqlSessionFactory;
    import org.apache.ibatis.session.SqlSessionFactoryBuilder;
    
    public class TestMybatis {
      public static void main(String[] args) throws IOException {
            //
          Reader reader=Resources.getResourceAsReader("conf.xml");
          SqlSessionFactory sqlSessionFactory=new SqlSessionFactoryBuilder().build(reader);
          //session -- connection
          SqlSession session = sqlSessionFactory.openSession();
            //通过id值来定位写在mapper中的SQL语句
          String statement = "com.whl.entity.PersonMapper.queryPersonById";
            //查询一条,用selectOne,第一个参数是sql,第二个参数是sql中的?
          Person person = session.selectOne(statement,1);
          System.out.println(person);
            //关闭数据库连接
          session.close();
      }
    }
    

Ⅱ、基础方式和mapper动态代理方式

1.基础方式

第一个mybatis程序使用的就是基础方式

<select id="queryPersonById" resultType="com.whl.entity.Person" parameterType="int">
        select * from person where id = #{id}
 </select>
<select id="queryAllPerson" resultType="com.whl.entity.Person">
        select * from person
 </select>
  • 输入参数parameterType和输出参数resultType,在形式上都只能有一个
  • 输入参数 :是简单类型(8个基本类型+String)是可以使用任何占位符,#{xxxx};如果是对象类型,则必须是对象的属性#{属性名}
  • 输出参数:如果返回值类型是一个 对象(如Student),则无论返回一个、还是多个,在resultType都写成org.lanqiao.entity.Student,即 resultType="org.lanqiao.entity.Student"
  • 如果使用的事务方式为jdbc,则需要手工commit提交,即session.commit();(增删改需要提交)
  • 所有的标签 <select> <update>等,都必须有sql语句,但是sql参数值可选

2.mapper动态代理方式

约定优于配置

与基础方式的不同点:

约定的目标:省略掉statement,即根据约定直接可以定位出SQL语句

接口:遵循以下规定

  1. 方法名和mapper.xml文件中标签的id值相同

  2. 方法的输入参数和mapper.xml文件中标签的parameterType类型一致(如果mapper.xml的标签中没有 parameterType,则说明方法没有输入参数)

  3. 方法的返回值和mapper.xml文件中标签的 resultType类型一致(无论查询结果是一个 还是多个(student、List<Student>),在mapper.xml标签中的resultType中只写 一个(Student);如果没有resultType,则说明方法的返回值为void)

personMapper.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">
<!--
与基础方式的不同
namespace写的是PersonMapper接口的全类名
-->
<mapper namespace="com.whl.mapper.PersonMapper">
    <select id="queryPersonById" resultType="com.whl.entity.Person" parameterType="int">
        select * from person where id = #{id}
    </select>
    
    <insert id="addPerson" parameterType="com.whl.entity.Person">
        insert into person(id,name,age) values(#{id},#{name},#{age})
    </insert>
    
    <delete id="deletePersonById" parameterType="int">
        delete from person where id = #{id}
    </delete>
    
    <update id="updatePersonById" parameterType="com.whl.entity.Person">
        update person set name=#{name},age=#{age} where id=#{id}
    </update>
    
    <select id="queryAllPerson" resultType="com.whl.entity.Person">
        select * from person
    </select>
</mapper>

PersonMapper接口

package com.whl.mapper;

import java.util.List;
import com.whl.entity.Person;

//操作mybatis的接口
public interface PersonMapper {
    Person queryPersonById(int id);
    List<Person> queryAllPerson();
    void addPerson(Person person);
    void deletePersonById(int id);
    void updatePersonById(Person person);
}

测试类

package com.whl.entity;


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

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.javassist.expr.NewArray;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import com.whl.mapper.PersonMapper;

public class Test {

    public static void queryPersonById(int id) throws IOException {
        Reader reader=Resources.getResourceAsReader("conf.xml");
        SqlSessionFactory sqlSessionFactory=new SqlSessionFactoryBuilder().build(reader);
        //session -- connection
        SqlSession session = sqlSessionFactory.openSession();
        PersonMapper personMapper=session.getMapper(PersonMapper.class);
        Person person=personMapper.queryPersonById(1);
        System.out.println(person);
        session.close();
    }
    
    public static void queryAllPerson() throws IOException {
        Reader reader=Resources.getResourceAsReader("conf.xml");
        SqlSessionFactory sqlSessionFactory=new SqlSessionFactoryBuilder().build(reader);

        SqlSession session = sqlSessionFactory.openSession();
        PersonMapper personMapper=session.getMapper(PersonMapper.class);
        List<Person> persons=personMapper.queryAllPerson();
        System.out.println(persons);
        session.close();
    }
    
    public static void addPerson(Person person) throws IOException {
        Reader reader=Resources.getResourceAsReader("conf.xml");
        SqlSessionFactory sqlSessionFactory=new SqlSessionFactoryBuilder().build(reader);   
        SqlSession session = sqlSessionFactory.openSession();
        PersonMapper personMapper=session.getMapper(PersonMapper.class);
        personMapper.addPerson(person);
        session.commit();
        System.out.println("增加成功");
        session.close();
    }
    
    public static void deletePersonById(int id) throws IOException {
        Reader reader=Resources.getResourceAsReader("conf.xml");
        SqlSessionFactory sqlSessionFactory=new SqlSessionFactoryBuilder().build(reader);   
        SqlSession session = sqlSessionFactory.openSession();
        PersonMapper personMapper=session.getMapper(PersonMapper.class);
        personMapper.deletePersonById(id);
        session.commit();
        System.out.println("删除成功");
        session.close();
    }
    
    public static void updatePersonById(Person person) throws IOException {
        Reader reader=Resources.getResourceAsReader("conf.xml");
        SqlSessionFactory sqlSessionFactory=new SqlSessionFactoryBuilder().build(reader);   
        SqlSession session = sqlSessionFactory.openSession();
        PersonMapper personMapper=session.getMapper(PersonMapper.class);
        personMapper.updatePersonById(person);
        session.commit();
        System.out.println("修改成功");
        session.close();
    }
    public static void main(String[] args) throws IOException {
        //queryPersonById(1);
        //addPerson(new Person(3,"xxx",19));
        //updatePersonById(new Person(2,"whl",19));
        deletePersonById(3);
        queryAllPerson();       
    }
}

测试类中最重要的代码

StudentMapper studentMapper = session.getMapper(StudentMapper.class) ;
studentMapper.方法();

通过session对象获取接口(session.getMapper(接口.class);),再调用该接口中的方法,程序会自动执行该方法对应的SQL。这样就不需要statement和selectone了

匹配的过程:

  1. 根据 接口名 找到 mapper.xml文件(根据的是namespace=接口全类名)
  2. 根据 接口的方法名 找到 mapper.xml文件中的SQL标签 (方法名=SQL标签Id值)

以上2点可以保证: 当我们调用接口中的方法时,程序能自动定位到某一个Mapper.xml文件中的sqL标签

习惯:SQL映射文件(mapper.xml)和接口放在同一个包中(注意修改conf.xml中加载mapper.xml文件的路径)

3.优化

  • 可以将配置信息单独放入db.properties文件中,然后再动态引入

    形式:kv对

    driver=com.mysql.cj.jdbc.Driver
    url=jdbc:mysql://localhost:3306/mysqldemo?serverTimezone=UTC
    username=root
    password=123456
    

    db.properties放在源路径

    在conf.xml中用properties标签引入

    <configuration>
        <properties resource="db.properties"/>
        ...
    </configuration>
    

    通过${key}来取值(类似于EL表达式)

    <property name="driver" value="${driver}"/>
    <property name="url" value="${url}"/>
    <property name="username" value="${username}"/>
    <property name="password" value="${password}"/>
    
  • MyBatis全局参数,在conf.xml中设置(一般不进行手动设置,使用默认值)

    在setting标签中进行设置

    <settings>
      <setting name="cacheEnabled" value="false"  />
      <setting name="lazyLoadingEnabled" value="false"  />
    </settings>
    
  • 别名(单个别名、批量别名),在conf.xml中的typeAliases标签进行设置

    <typeAliases>
          <!-- 单个别名 (别名 忽略大小写) -->
          <!-- <typeAlias type="org.lanqiao.entity.Student" alias="student"/> -->
          <!--  批量定义别名  (别名 忽略大小写),以下会自动将该包中的所有类 批量定义别名: 别名就是类名(不带包名,忽略大小写)   -->
          <package name="org.lanqiao.entity"/>
    </typeAliases>
    

    除了自定义别名外,MyBatis还内置了一些常见类的别名。

Ⅲ、类型处理器

类型处理器:把Java代码中的类型转换成JDBC类型

1.自带类型处理器

MyBatis自带一些常见的类型处理器

image-20200819110702060.png

2.自定义MyBatis类型处理器

例:

实体类Student :  boolean   stuSex;
true:男
false:女
表student:   number  stuSex
1:男
0:女
  1. 创建转换器

    • 直接实现TypeHandler接口

    • 或继承BaseTypeHandler

      package org.lanqiao.converter;
      
      import java.sql.CallableStatement;
      import java.sql.PreparedStatement;
      import java.sql.ResultSet;
      import java.sql.SQLException;
      
      import org.apache.ibatis.type.BaseTypeHandler;
      import org.apache.ibatis.type.JdbcType;
      
      //BaseTypeHandler<java类型>
      public class BooleanAndIntConverter extends BaseTypeHandler<Boolean>{
      
        //java(boolean)-DB(number)
        /*
         * ps:PreparedStatement对象
         * i:PreparedStatement对象操作参数的位置
         * parameter:java值
         * jdbcType:jdbc操作的数据库类型
         */
        @Override
        public void setNonNullParameter(PreparedStatement ps, int i, Boolean parameter, JdbcType jdbcType)
                throws SQLException {
                if(parameter) {
                    //1
                    ps.setInt(i, 1); 
                }else {
      //                0
                    ps.setInt(i, 0); 
                }
        }
      
        //db(number)->java(boolean)
        @Override
        public Boolean getNullableResult(ResultSet rs, String columnName) throws SQLException {
            int sexNum = rs.getInt(columnName) ;//rs.getInt("stuno") ;
      
            return sexNum == 1?true:false ;
        }
      
        @Override
        public Boolean getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
            int sexNum = rs.getInt(columnIndex) ;//rs.getInt(1)
            return sexNum == 1?true:false ;
        }
      
        @Override
        public Boolean getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
            int sexNum = cs.getInt(columnIndex) ;//rs.getInt(1)
            return sexNum == 1?true:false ;
        }
      }
      
  1. 配置conf.xml,通过typeHandler标签配置类型转换器

    <typeHandlers>
     <typeHandler handler="org.lanqiao.converter.BooleanAndIntConverter" javaType="Boolean" jdbcType="INTEGER" />
    </typeHandlers>
    
  2. 配置mapper.xml

    <select id="queryStudentByStunoWithConverter"    parameterType="int"     
     resultMap="studentResult" ><!--注意这里使用了resultMap而不是resultType-->
         select * from student where stuno = #{stuno}
    </select>
    <!--配置了一些表字段名到属性名的映射-->  
    <resultMap type="student" id="studentResult">
         <!-- 分为主键id 和非主键 result-->
         <id property="stuNo"  column="stuno"  />
         <result property="stuName"  column="stuname" />
         <result property="stuAge"  column="stuage" />
         <result property="graName"  column="graname" />
         <result property="stuSex"  column="stusex"  javaType="boolean" jdbcType="INTEGER"/>
    </resultMap>
    

    何时使用resultMap?

    • 如果类中属性名和表中的字段名能够合理识别(stuNo -stuno)则可以使用resultType;否则(id-stuno)使用resultMap
    • 如果类中属性和表中的字段类型能够合理识别(String-varchar2),则可以使用resultType;否则(boolean-number)使用resultMap

Ⅳ、输入参数

1.简单类型

  • {}、${}的区别

    1. {任意值}

      ${value} ,其中的标识符只能是value

    2. {}自动给String类型加上' '(自动类型转换)

      ${}原样输出,但是适合于动态排序(动态字段)

      select stuno,stuname,stuage  from student where stuname = #{value}
      select stuno,stuname,stuage  from student where stuname = '${value}'
      动态排序:
      select stuno,stuname,stuage  from student  order by ${value} asc
      
    3. {}可以防止SQL注入,${}不防止

  • 相同之处:都可以获取对象的值(嵌套类型对象)

2.模糊查询获取对象值

  • 方式一:用#{}
  select stuno,stuname,stuage  from student where stuage= #{stuAge}  or stuname like #{stuName} 
  Student student = new Student();
  student.setStuAge(24);
  student.setStuName("%w%");//在set时就加入了通配符
  List<Student> students = studentMapper.queryStudentBystuageOrstuName(student) ;//接口的方法->SQL
  • 方式二:用${}

    student.setStuName("w");
    
    select stuno,stuname,stuage  from student where stuage= #{stuAge}  or stuname like '%${stuName}%'
    

3.输入对象为HashMap

在框架学习中HashMap一般是这样声明的(String,Object对)

Map<String,Object> map = new HashMap<>();
map.put("stuAge",19);
<select id="..." parameterType="HashMap" resultType="...">
    select ... where stuage= #{stuAge}<!--通过stuAge取到了map中的值作为参数-->
</select>

Ⅴ、输出参数

输出参数的四种情况:

  1. 简单类型(8个基本+String)
  2. 输出参数为实体对象类型
  3. 输出参数为实体对象类型的集合 :虽然输出类型为集合,但是resultType依然写 集合的元素类型(resyltType="Student")
  4. 输出参数类型为HashMap
    --HashMap本身是一个集合,可以存放多个元素,
    但是根据提示发现 返回值为HashMap时 ,查询的结果只能是1个学生(no,name);
    -->结论:一个HashMap 对应一个学生的多个元素(多个属性) [一个map,一个学生]

1.resultMap

当实体类的属性、数据表的字段:类型、名字不同时,使用resultMap建立起字段名与属性名的映射

<resultMap type="student" id="queryStudentByIdMap">
    <!-- 指定类中的属性和表中的字段对应关系-->
    <id property="stuNo"  column="id" />
    <result property="stuName" column="name" />
</resultMap>
  • 用id标签指定主键的映射
  • 用result标签指定非主键的映射
  • property属性的值为属性名,column属性的值为字段名

2.resultType+HashMap

通过hashmap把属性名与字段名进行对应

  • 可用AS取别名的方式进行对应,也可以在select语句字段名后空格给出属性名
<!--注意一定要把返回类型改成hashmap-->
<select id="queryPersonByIdWithHashMap" parameterType="int" resultType="hashmap">
    select id as personId,name as personName,age as personAge from person where id=#{id}
 </select>
或
<select id="queryPersonByIdWithHashMap" parameterType="int" resultType="hashmap">
    select id "personId",name "personName",age "personAge" from person where id=#{id}
 </select>
<!--双引号可加可不加-->
HashMap<String, Object> person=personMapper.queryPersonByIdWithHashMap(1);
System.out.println(person);
//{personName=lxl, personAge=19, personId=1}

注意:以hashmap作为返回类型,其中hashmap存储的是某一个学生的多个属性的键值对,而不是多个学生

如何返回多个学生?List<HashMap<String, Object>> persons

List<HashMap<String, Object>> persons = personMapper.queryPersonAllWithHashMap();
System.out.println(persons);
//[{name=lxl, id=1, age=19}, {name=whl, id=2, age=19}]

Ⅵ、动态SQL

1.动态生成where子句

<select id="queryPersonByNameAndAge" parameterType="person" resultMap="person_map">
    select id,name,age from person
    <where>
        <if test="personName!=null and personName!=''">
            and name = #{personName}
        </if>
        <if test="personAge!=null and personAge!=0">
            and age = #{personAge}
        </if>
    </where>        
 </select>
    
 <resultMap type="person" id="person_map">
     <id property="personId" column="id"/>
     <result property="personName" column="name"/>
     <result property="personAge" column="age"/>
 </resultMap>
  • where标签:写了后就不用在SQL中手写where关键字了
  • if标签
    • test属性写条件表达式,为true时执行标签内的SQL
    • 注意:条件表达式中写的是对属性名的判断,不同条件用and、or连接
    • 第一个if标签中的SQL是可以加and关键字的,因为mybatis会自动忽略这个and关键字

我踩的坑:怎么执行查询都是null,后来发现没有配置resultMap。只要是字段和属性名不一样就必须配置!

2.输入参数为集合或数组

输入参数是list的情况

 <!--属性名和字段的映射-->   
<resultMap type="person" id="person_map">
        <id property="personId" column="id"/>
        <result property="personName" column="name"/>
        <result property="personAge" column="age"/>
 </resultMap>

 <select id="queryPersonByNolist" parameterType="list" resultMap="person_map">
    select * from person
    <where>
        <if test="list!=null and list.size>0">
            <foreach collection="list" open=" and id in (" close=")" item="personNo" separator=",">
                #{personNo}
            </foreach>
        </if>
    </where>
 </select>
  • 所有集合在mapper中都用list来指代,并不使用集合本身的名称
  • foreach标签用于迭代集合
    • collection属性:指定要遍历的集合
    • open和close:只遍历集合中元素,不遍历集合前后的SQL语句。用这两个属性指定前后的SQL
    • item:给集合中每个元素取一个临时的名称。这个名称是无所谓的,只要和标签中#{}的一样就行了
    • separator:分隔符,不加就会变成"id in (123)",需要的是"id in (1,2,3)"
  • 用#{}来取集合中元素的值
personNos.add(1);
personNos.add(2);
//personNos.add(3);
List<Person> persons=personMapper.queryPersonByNolist(personNos);;
System.out.println(persons);
//[Person [personId=1, personName=lxl, personAge=19], Person [personId=2, personName=whl, personAge=19]]

输入参数是int数组的情况:

 <select id="queryPersonByNoarray" parameterType="int[]" resultMap="person_map">
    select * from person
    <where>
        <if test="array!=null and array.length>0">
            <foreach collection="array" open=" and id in (" close=")" item="personNo" separator=",">
                #{personNo}
            </foreach>
        </if>
    </where>
</select>
  • 需要注意集合的大小是size,数组是length
  • 传入的简单类型的数组在mapper中一律以array表示
int[] personNos=new int[] {1,2,3};
List<Person> persons=personMapper.queryPersonByNoarray(personNos);;
System.out.println(persons);
//[Person [personId=1, personName=lxl, personAge=19], Person [personId=2, personName=whl, personAge=19], Person [personId=3, personName=abc, personAge=19]]

输入参数是对象数组的情况:

<select id="queryPersonByObjectarray" parameterType="Object[]" resultMap="person_map">
    select * from person
    <where>
        <if test="array!=null and array.length>0">
            <foreach collection="array" open=" and id in (" close=")" item="person" separator=",">
                #{person.personId}
            </foreach>
        </if>
    </where>
 </select>
  • 注意:所有类的对象数组的parameterType都是Object[]
  • 级联获取对象中的属性值
Person p1=new Person();
Person p2=new Person();
Person p3=new Person();
p1.setPersonId(1);
p2.setPersonId(2);
p3.setPersonId(3);
Person[] persons1=new Person[] {p1,p2,p3};
List<Person> result=personMapper.queryPersonByObjectarray(persons1);;
System.out.println(result);
//[Person [personId=1, personName=lxl, personAge=19], Person [personId=2, personName=whl, personAge=19], Person [personId=3, personName=abc, personAge=19]]

3.提取SQL片段

<sql id="sql1">
    select * from person
 </sql>

 <select id="queryPersonByObjectarray" parameterType="Object[]" resultMap="person_map">
    <include refid="sql1"></include>
    <where>
        <if test="array!=null and array.length>0">
            <foreach collection="array" open=" and id in (" close=")" item="person" separator=",">
                #{person.personId}
            </foreach>
        </if>
    </where>
 </select>
  • 用sql标签提取出常用的SQL语句,id标识唯一的SQL语句
  • 用include标签中的refid属性指定要使用的SQL语句的id

Ⅶ、关联查询

image-20200822120148468.png
image-20200822120206489.png

一对一:association
一对多:collection

1.一对一

方法一:

1.建一个类,继承自属性多的类,再把多出来的属性写在这个新类里

package com.whl.entity;

public class PersonBusiness extends Person{
    private int cardId;
    private String cardInfo;
    
    public int getCardId() {
        return cardId;
    }
    public void setCardId(int cardId) {
        this.cardId = cardId;
    }
    public String getCardInfo() {
        return cardInfo;
    }
    public void setCardInfo(String cardInfo) {
        this.cardInfo = cardInfo;
    }
    @Override
    public String toString() {
        return super.toString()+"PersonBusiness [cardId=" + cardId + ", cardInfo=" + cardInfo + "]";
    }   
}
  • 注意tostring方法要重写并调用父类的tostring!

2.设置person表的cardid为外键

3.写SQL

<select id="queryPersonByIdOO" parameterType="int" resultType="PersonBusiness">
    select p.*,c.* from person as p inner join personcard as c
    on p.cardid=c.cardid
    where p.id=#{id}
 </select>
  • 注意返回类型为PersonBusiness(第三个类)

4.执行

PersonBusiness personBusiness=personMapper.queryPersonByIdOO(1);
System.out.println(personBusiness);
//Person [id=1, name=lxl, age=19]PersonBusiness [cardId=1, cardInfo=lxl info...]

我踩的坑:第一次查询结果只显示了PersonBusiness增加的属性,person中的显示都为null或者0,最后发现是数据库字段名与属性名不一致。修改一致后就可正常显示

方法二:写一个person类和personcard类,在person类中有一个personcard对象

public class PersonCard {
    private int cardId;
    private String cardInfo;
}

public class Person {
    private int id;
    private String name;
    private int age;
    private PersonCard card;//把PersonCard类的对象作为Person类的属性
}
<select id="queryPersonByIdOO" parameterType="int" resultMap="person_card_map">
    select p.*,c.* from person as p inner join personcard as c
    on p.cardid=c.cardid
    where p.id=#{id}
 </select>
    
 <resultMap type="person" id="person_card_map">
    <id property="id" column="id"/>
    <result property="name" column="name"/>
    <result property="age" column="age"/>
    <association property="card" javaType="PersonCard" >
        <id property="cardId" column="cardid"/>
        <result property="cardInfo" column="cardinfo"/>
    </association>
 </resultMap>
  • resultMap中的type属性为person,因为person包含了PersonCard
  • 一对一关联:用association标签指定一对一
    • property属性的值为Person类中PersonCard对象成员的名称
    • javaType属性指定了card对象的类名
    • id和result标签指定了personcard表与personcard对象字段名、属性名之间的映射关系
Person person=personMapper.queryPersonByIdOO(1);
System.out.println(person);
//Person [id=1, name=lxl, age=19, card=PersonCard [cardId=1, cardInfo=lxl info...]]

2.一对多

1.写xml

<select id="queryPersonByClassId" parameterType="int" resultMap="person_class_map">
    select c.*,p.* from person as p
    inner join personclass c
    on c.classid=p.classid
    where c.classid=#{classid}
 </select>
    
 <resultMap type="PersonClass" id="person_class_map">
    <id property="classId" column="classid"/>
    <result property="className" column="classname"/>   
    <collection property="persons" ofType="Person">
        <id property="id" column="id"/>
        <result property="name" column="name"/>
        <result property="age" column="age"/>
    </collection>
 </resultMap>
  • collection标签:指定一对多关联查询

2.写属性类

public class PersonClass {
    private int classId;
    private String className;
    List<Person> persons;
    ...
}

public class Person {
    private int id;
    private String name;
    private int age;
    private PersonCard card;
    ...
}
  • 班级和学生为一对多关系,所以在PersonClass中用List保存学生
  • card对象作为Person类的成员

3.测试运行

PersonClass personClass=personMapper.queryPersonByClassId(1);
System.out.println(personClass);
//PersonClass [classId=1, className=c1, persons=[Person [id=1, name=lxl, age=19, card=null]]]

Ⅷ、日志

可以通过日志信息,相信的阅读mybatis执行情况(观察mybatis实际执行sql语句以及SQL中的参数和返回结果)

以log4j为例

  1. 引入log4j.jar

  2. 在conf.xml中开启日志

    <settings>
     <!-- 开启日志,并指定使用的具体日志 -->
     <setting name="logImpl" value="LOG4J"/> 
    </settings>
    

    如果不指定,Mybatis就会根据以下顺序 寻找日志
    SLF4J →Apache Commons Logging →Log4j 2 → Log4j →JDK logging

  3. 编写配置日志输出文件

    在src下创建log4j.properties

    log4j.rootLogger=DEBUG, stdout
    log4j.appender.stdout=org.apache.log4j.ConsoleAppender
    log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
    log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n
    

日志级别:DEBUG<INFO<WARN<ERROR

如果设置为info,则只显示 info及以上级别的信息;
建议:在开发时设置debug,在运行时设置为info或以上。

Ⅸ、延迟加载

也称“懒加载”

什么是延迟加载:

在关联查询(一对一、一对多、多对多)中,如果不采用延迟加载(立即加载),查询时会将一和多都查询,班级、班级中的所有学生。如果想要暂时只查询1的一方,而多的一方先不查询而是在需要的时候再去查询-->延迟加载

  1. 开启延迟加载conf.xml配置settings

    <settings>
     <!-- 开启延迟加载 -->
     <setting name="lazyLoadingEnabled" value="true"/>
         
     <!-- 关闭立即加载 -->
     <setting name="aggressiveLazyLoading" value="false"/>
    </settings>
    
  2. 写接口

    List<Person> queryPersonByIdOO();
    
  3. 写SQL

    <select id="queryPersonByIdOO"  resultMap="person_card_map">
         select * from person
     </select>
         
     <resultMap type="person" id="person_card_map">
         <id property="id" column="id"/>
         <result property="name" column="name"/>
         <result property="age" column="age"/>
         <association property="card" javaType="com.whl.entity.PersonCard" select="com.whl.mapper.PersonCardMapper.queryCardById" column="cardid">
     </association>
     </resultMap>
    

    新建PersonCardMapper,注意要在conf.xml加载该映射!

    <mapper namespace="com.whl.mapper.PersonCardMapper">
         <select id="queryCardById" parameterType="int" resultType="com.whl.entity.PersonCard">
             select * from personcard where cardid = #{cardId}
         </select>
    </mapper>
    
    • association标签表示一对一,collection表示一对多
      • property表示person类中的card属性
      • javaType写card的类名
      • select属性写PersonCardMapper中的sql的id。形式:namespace.id
      • column关联的外键,作为queryCardById的输入参数
    • queryCardById中的resultType属性:写card类的全名
  4. 测试运行

    List<Person> persons=personMapper.queryPersonByIdOO();
    System.out.println(persons);
    for(Person person:persons) {
     //System.out.println(person.getId()+"----"+person.getName());
     PersonCard personCard=person.getCard();
     System.out.println(person);
    }
    //Person [id=1, name=lxl, age=19, card=PersonCard [cardId=1, cardInfo=lxl info...]]
    //Person [id=2, name=whl, age=19, card=PersonCard [cardId=2, cardInfo=whl info...]]
    

Ⅹ、查询缓存

一级缓存 :同一个SqlSession对象

MyBatis默认开启一级缓存,如果用同样的SqlSession对象查询相同的数据,则只会在第一次 查询时 向数据库发送SQL语句,并将查询的结果 放入到SQLSESSION中(作为缓存存在);后续再次查询该同样的对象时,则直接从缓存中查询该对象即可(即省略了数据库的访问)

二级缓存:MyBatis默认情况没有开启二级缓存,需要手工打开。
1.conf.xml

<!-- 开启二级缓存 -->
<setting name="cacheEnabled" value="true"/>

2.在具体的mapper.xml中声明开启(studentMapper.xml中)

<mapper namespace="org.lanqiao.mapper.StudentMapper">
    <cache/>
<mapper/>
  • cache标签:声明此namespace开启二级缓存

根据异常提示:NotSerializableException可知,MyBatis的二级缓存 是将对象 放入硬盘文件中

准备缓存的对象,必须实现了序列化接口(如果开启的缓存Namespace="org.lanqiao.mapper.StudentMapper"),可知序列化对象为Student,因此需要将Student序列化(序列化Student类,以及Student的级联属性、和父类)

  • 触发将对象写入二级缓存的时机:SqlSession对象的close()方法。

Mybatis自带二级缓存:[同一个namespace]生成的mapper对象

回顾:namespace的值就是接口的全类名(包名.类名),通过接口可以产生代理对象(studentMapper对象)

namespace决定了studentMapper对象的产生

结论:只要产生的xxxMapper对象 来自于同一个namespace,则 这些对象 共享二级缓存。

注意:二级缓存 的范围是同一个namespace, 如果有多个xxMapper.xml的namespace值相同,则通过这些xxxMapper.xml产生的xxMapper对象仍然共享二级缓存。

  • 禁用 :select标签中useCache="false"

清理:

1.与清理一级缓存的方法相同
commit(); (一般执行增删改时 会清理掉缓存;设计的原因 是为了防止脏数据)
在二级缓存中,commit()不能是查询自身的commit。

commit会清理一级和二级缓存;但是 清理二级缓存时,不能是查询自身的commit;
2. 在select标签中 增加属性 flushCache="true"

命中率:缓存中存在就是命中。命中数/查询数

三方提供的二级缓存:ehcache、memcache

要想整合三方提供的二级缓存 (或者自定义二级缓存),必须实现org.apache.ibatis.cache.Cache接口,该接口的默认实现类是PerpetualCache

整合ehcache二级缓存:

  1. ehcache-core.jar mybatis-Ehcache.jar slf4j-api.jar
  2. 编写ehcache配置文件 Ehcache.xml
  3. 开启EhCache二级缓存
    在xxxMapper.xml中开启
<cache  type="org.mybatis.caches.ehcache.EhcacheCache">
    <!-- 通过property覆盖Ehcache.xml中的值 -->
    <property name="maxElementsInMemory" value="2000"/>
    <property name="maxElementsOnDisk" value="3000"/>
</cache>
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,406评论 6 503
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,732评论 3 393
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,711评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,380评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,432评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,301评论 1 301
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,145评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,008评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,443评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,649评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,795评论 1 347
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,501评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,119评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,731评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,865评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,899评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,724评论 2 354