4.3 映射器(Mapper) sql、参数、resultMap、关联标签及延时加载详解

1.sql

这个标签元素可以被用来定义可重用的SQL代码段,可以包含在其他语句中。也可以被静态地(在加载参数)参数化。不同的属性值通过包含的实例变化。

 ${alias}.id,${alias}.username,${alias}.password 

通过include关键字进行引用sql元素。

1.1 refid属性也可以使用属性值表示


     select * from 



    ${tablename}



2.参数(Parameters)

定制参数属性时候,MyBatis不支持换行。

2.1简单的参数值(int,long,float等)


2.2Java POJO作为参数值


  insert into users (id, username, password)
  values (#{id}, #{username}, #{password})

User 类型的参数对象传递到了语句中,id、username 和 password 属性将会被查找,然后将它们的值传入预处理语句的参数中。

2.3参数配置

参数也可以指定一个特殊的数据类型

#{property,javaType=int,jdbcType=NUMERIC}

对于数值类型,还有一个小数保留位数的设置,来确定小数点后保留的位数。

#{height,javaType=double,jdbcType=NUMERIC,numericScale=2}

2.4 特殊字符串替换和处理(#和$)

默认情况下,使用#{}格式的语法会导致MyBatis创建预处理语句属性并安全地设置值(比如?)

这样做更安全,更迅速,通常也是首选做法,不过有时你只是想直接在 SQL语句中插入一个不改变的字符串。比如,像 ORDER BY,你可以这样来使用:

ORDER BY ${columnName}

这里 MyBatis 不会修改或转义字符串。

注意: 以这种方式接受从用户输出的内容并提供给语句中不变的字符串是不安全的,会导致潜在的SQL注入攻击,因此要么不允许用户输入这些字段,要么自行转义并检验。

3.resultMap结果映射集

resultMap是MyBatis里面最复杂的元素。它的作用是定义映射规则级联更新定制类型转化器等。

3.1 resultMap元素的构成

resultMap是由很多子元素构成的。


  • 1.constructor

用于配置构造方法。主要用于告知MyBatis在把数据库结果集转化成POJO对象时候,使用对应的构造方法。

主要属性

idArg:ID 参数;标记结果作为 ID 可以帮助提高整体效能

arg:注入到构造方法的一个普通结果

public class User {
    private int id;

    private String name;

    private int age;

    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }
}

        
            
            
        
        
        
        
    
  • 2.id

id元素是表示哪个列是主键,允许多个主键,多个主键则称为联合主键。实际项目中,最好避免使用联合主键。会导致项目变的复杂。

  • 3.result

result元素是配置POJO到SQL列名的映射关系。

id与result的元素属性如下:

property

数据库列映射到哪个字段或者属性上。如果数据库列名(column)与pojo的属性一致。就可以无须额外配置。

column

这里对应的时数据库里面定义的列名。

javaType

配置POJO的Java类型

jdbcType

配置数据库列类型

typeHandler

类型处理器。可以使用自定义的类型处理器。

  • 4.级联

级联中存在3种对应关系。

1.association(一对一关系) 2.collection(一对多关系) 3.discriminator(鉴别器)它可以根据实际选择采用哪个类作为实例。允许你根据特定的条件去关联不同的结果集。(在下面单独讲解)

3.2 使用map存储结果集(不推荐)

一般而言,任何select语句都可以使用map存储。

  • dao层定义
public interface IUserDao {
    Map getUserByIdResultMap(int id);
}
  • mapper sql语句

  • 输出结果
{name=jack, age=21}

3.3 使用POJO存储结果集(推荐)

使用map方式会降低可读性。POJO是最常用的方法。一方面可以使用自动映射,或者使用select 元素属性的resultMap配置映射集合。


    
    
    




4.级联

4.1 association(一对一关系)

比如博客系统里面,博客空间站点与作者一对一映射关系。

4.2 collection (一对多的关系)

比如博客系统里面,一个博客站点可以对应多个博文。

4.3 discriminator (鉴别器)

其实翻译为"鉴别器"不太妥,因为这个元素主要处理根据不同条件返回不同列的结果集。类似java语言中的switch语句。


  
  
  
  
  
  
  
    
    
    
    
  

上面的示例就是value值不同,返回不是结果的列。

5.级联案例分析

让我们通过blog系统的来全面掌握上面三种关联关系。

  • 创建数据库
CREATE DATABASE IF NOT EXISTS cnblogs DEFAULT CHARSET utf8 COLLATE utf8_general_ci;
  • 创建表
CREATE TABLE blog (
  blog_id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '自增主键',
  blog_title VARCHAR (50) DEFAULT '' COMMENT '博客空间名称',
  blog_author_id BIGINT not null default 0 comment '用户id'
) ENGINE = INNODB CHARSET utf8
CREATE TABLE author (
  author_id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '自增主键',
  author_username VARCHAR (50) DEFAULT '' COMMENT '用户名',
  author_password VARCHAR (50) DEFAULT '' COMMENT '密码',
  author_email VARCHAR (50) DEFAULT '' COMMENT '邮箱',
  author_bio VARCHAR (500) DEFAULT '个人简介'
) ENGINE = INNODB CHARSET utf8
  • java pojo
package com.cnblogs.model;

import org.apache.ibatis.type.Alias;

/**
 * 作者
 */
@Alias("author")
public class Author {
    /**
     * 自增主键
     */
    private long id;

    /**
     * 用户名
     */
    private String userName;

    /**
     * 密码
     */
    private String password;

    /**
     * 邮箱
     */
    private String email;

    /**
     * 个人简介
     */
    private String bio;

   //省略getter setter

    @Override
    public String toString() {
        return "Author{" +
                "id=" + id +
                ", userName='" + userName + '\'' +
                ", password='" + password + '\'' +
                ", email='" + email + '\'' +
                ", bio='" + bio + '\'' +
                '}';
    }
}

package com.cnblogs.model;

import org.apache.ibatis.type.Alias;

/**
 * 博客站点
 */
@Alias("blog")
public class Blog {
    /**
     * 自增主键
     */
    private long id;

    /**
     * 博客空间名称
     */
    private String title;

    /**
     * 用户id
     */
    private long authorId;

    private Author author;

   //省略getter setter

    @Override
    public String toString() {
        return "Blog{" +
                "id=" + id +
                ", title='" + title + '\'' +
                ", authorId=" + authorId +
                ", author=" + author +
                '}';
    }
}

  • 创建dao
package com.cnblogs.dao;

import com.cnblogs.model.Author;

public interface AuthorDao {
    Author selectAuthor(int id);

    int insert(Author author);
}
package com.cnblogs.dao;

import com.cnblogs.model.Blog;

public interface BlogDao {
    Blog selectBlog(int id);

    int insert(Blog author);
}

  • 创建mybatis全局配置文件




    
    
    
    
        
    
    
    
        
    
    
    
    
    
    
    
    
    
    
    
        
        
            
            
            
            
                
                
                
                
            
        
    
    
    
    
    
        
        
    

  • 创建datasource.properties
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/cnblogs?serverTimezone=Asia/Shanghai&characterEncoding=utf8
jdbc.username=root
jdbc.password=root
  • 创建mapper文件



    
        
        
        
        
        
    

    

    
        INSERT INTO author (
        author_username,
        author_password,
        author_email,
        author_bio
        )
        VALUES
        (
        #{userName},
        #{password},
        #{email},
        #{bio}
        );
    




    
        
        
        
        
    

    

    
        INSERT INTO blog (blog_title,blog_author_id) VALUES (#{title},#{authorId});
    

  • clinet测试
package com.cnblogs.client;

import com.cnblogs.dao.AuthorDao;
import com.cnblogs.dao.BlogDao;
import com.cnblogs.model.Author;
import com.cnblogs.model.Blog;
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;

public class Main {
    public static void main(String[] args) {
        SqlSessionFactory factory = null;
        SqlSession sqlSession = null;
        try {
            factory = buildSqlSessionFactory();
            sqlSession = factory.openSession();
            AuthorDao authorDao = sqlSession.getMapper(AuthorDao.class);
            BlogDao blogDao=sqlSession.getMapper(BlogDao.class);
//            Author author = buildAuthor1();
//            authorDao.insert(author);

//            Blog blog=buildBlog();
//            blogDao.insert(blog);


            Blog blog=blogDao.selectBlog(1);
            System.out.printf(blog.toString());
            sqlSession.commit();
        } catch (Exception ex) {
            ex.printStackTrace();
            sqlSession.rollback();
        }
    }

    public static SqlSessionFactory buildSqlSessionFactory() throws IOException {
        InputStream inputStream = Resources.getResourceAsStream("config/mybatis-config.xml");
        return new SqlSessionFactoryBuilder().build(inputStream);
    }

    public static Author buildAuthor() {
        Author author = new Author();
        author.setUserName("风凌天下");
        author.setPassword("fltx123456");
        author.setEmail("[email protected]");
        author.setBio("哥就是传说!");
        return author;
    }


    public static Author buildAuthor1() {
        Author author = new Author();
        author.setUserName("天蚕土豆");
        author.setPassword("tctd123456");
        author.setEmail("[email protected]");
        author.setBio("我是异火之父!");
        return author;
    }

    public static Blog buildBlog(){
        Blog blog=new Blog();
        blog.setTitle("风凌天下的博客站点");
        blog.setAuthorId(1);
        return blog;
    }
}

  • 结果
Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@2eda0940]
==>  Preparing: select * from blog where blog_id=?; 
==> Parameters: 1(Integer)
<==    Columns: blog_id, blog_title, blog_author_id
<==        Row: 1, 风凌天下的博客站点, 1
====>  Preparing: select * from author where author_id=?; 
====> Parameters: 1(Long)
<====    Columns: author_id, author_username, author_password, author_email, author_bio
<====        Row: 1, 风凌天下, fltx123456, [email protected], 哥就是传说!
<====      Total: 1
<==      Total: 1
Blog{id=1, title='风凌天下的博客站点', authorId=0, author=Author{id=1, userName='风凌天下', password='fltx123456', email='[email protected]', bio='哥就是传说!'}}

5.1 association分析

根据上面例子,我们在blog的pojo里面定义了author属性。那么我们在查询blog的时候如何带出author的属性值呢?

在mapper映射文件里面resultMap里面使用association子元素


    
    
    
    

association的column代表的是blog表里面存放authorid的列名。property代表的时blog的定义的author属性,

5.2 collection讲解

一个博客站点可以写多篇博文。博客站点与文章的对应关系是一对多。

  • 创建数据库
CREATE TABLE post (
    post_id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '自增主键',
    post_blog_id BIGINT DEFAULT 0 COMMENT '博客站点id',
    post_subject VARCHAR(50) DEFAULT '' COMMENT '文章标题',
    post_body VARCHAR(50) DEFAULT '' COMMENT '文章内容'
)  ENGINE=INNODB CHARSET UTF8
  • pojo
@Alias("post")
public class Post {
    private long id;

    private long blogId;

    private String subject;

    private String body;

    //省略getter setter

    @Override
    public String toString() {
        return "Post{" +
                "id=" + id +
                ", bloId=" + blogId +
                ", subject='" + subject + '\'' +
                ", body='" + body + '\'' +
                '}';
    }
}
  • 创建dao
package com.cnblogs.dao;

import com.cnblogs.model.Post;


import java.util.List;

public interface PostDao {
    int insert(Post post);

    int batchInsert(List posts);

    List selectPosts(int postId);
}


  • 新建mapper.xml



    
        
        
        
        
    

    

    
        INSERT INTO `cnblogs`.`post`
        (
        `post_blog_id`,
        `post_subject`,
        `post_body`)
        VALUES
        (
        #{postId}
        #{subject},
        #{body});
    

    
        INSERT INTO `cnblogs`.`post`
        (
        `post_blog_id`,
        `post_subject`,
        `post_body`)
        VALUES
        
            (#{item.blogId}, #{item.subject}, #{item.body})
        
    

  • 插入数据
public static List buildPosts() {
    List posts = new ArrayList<>();
    Post post = new Post();
    post.setPostId(1);
    post.setSubject("异世邪君");
    post.setBody("世间毁誉,世人冷眼,与我何干,我自淡然一笑。。。");
    posts.add(post);

    Post post1 = new Post();
    post1.setPostId(1);
    post1.setSubject("我是至尊");
    post1.setBody("药不成丹只是毒,人不成神终成灰。。。");
    posts.add(post1);
    return posts;
}
public static void main(String[] args) {
    SqlSessionFactory factory = null;
    SqlSession sqlSession = null;
    try {
        factory = buildSqlSessionFactory();
        sqlSession = factory.openSession();
        AuthorDao authorDao = sqlSession.getMapper(AuthorDao.class);
        BlogDao blogDao = sqlSession.getMapper(BlogDao.class);
        PostDao postDao = sqlSession.getMapper(PostDao.class);
        List posts = buildPosts();
        postDao.batchInsert(posts);
        sqlSession.commit();
    } catch (Exception ex) {
        ex.printStackTrace();
        sqlSession.rollback();
    }
}

blog关联多篇post文章

  • blog pojo加入posts属性
@Alias("blog")
public class Blog {
    /**
     * 自增主键
     */
    private long id;

    /**
     * 博客空间名称
     */
    private String title;

    /**
     * 用户id
     */
    private long authorId;

    private Author author;

    private List posts;

    //省略getter setter
    @Override
    public String toString() {
        return "Blog{" +
                "id=" + id +
                ", title='" + title + '\'' +
                ", authorId=" + authorId +
                ", author=" + author +
                ", posts=" + posts +
                '}';
    }
}
  • 修改blogmapper.xml



    
        
        
        
        

        
    

    

    
        INSERT INTO blog (blog_title,blog_author_id) VALUES (#{title},#{authorId});
    

  • 测试
public static void main(String[] args) {
        SqlSessionFactory factory = null;
        SqlSession sqlSession = null;
        try {
            factory = buildSqlSessionFactory();
            sqlSession = factory.openSession();
            AuthorDao authorDao = sqlSession.getMapper(AuthorDao.class);
            BlogDao blogDao = sqlSession.getMapper(BlogDao.class);
            PostDao postDao = sqlSession.getMapper(PostDao.class);
            Blog blog = blogDao.selectBlog(1);
            System.out.printf(blog.toString());
            sqlSession.commit();
        } catch (Exception ex) {
            ex.printStackTrace();
            sqlSession.rollback();
        }
    }
  • 结果
Created connection 1375995437.
Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@5204062d]
==>  Preparing: select * from blog where blog_id=?; 
==> Parameters: 1(Integer)
<==    Columns: blog_id, blog_title, blog_author_id
<==        Row: 1, 风凌天下的博客站点, 1
====>  Preparing: select * from post where post_blog_id=?; 
====> Parameters: 1(Long)
<====    Columns: post_id, post_blog_id, post_subject, post_body
<====        Row: 1, 1, 异世邪君, 世间毁誉,世人冷眼,与我何干,我自淡然一笑。。。
<====        Row: 2, 1, 我是至尊, 药不成丹只是毒,人不成神终成灰。。。
<====      Total: 2
====>  Preparing: select * from author where author_id=?; 
====> Parameters: 1(Long)
<====    Columns: author_id, author_username, author_password, author_email, author_bio
<====        Row: 1, 风凌天下, fltx123456, [email protected], 哥就是传说!
<====      Total: 1
<==      Total: 1
Blog{id=1, title='风凌天下的博客站点', authorId=1, author=Author{id=1, userName='风凌天下', password='fltx123456', email='[email protected]', bio='哥就是传说!'}, posts=[Post{id=1, bloId=1, subject='异世邪君', body='世间毁誉,世人冷眼,与我何干,我自淡然一笑。。。'}, Post{id=2, bloId=1, subject='我是至尊', body='药不成丹只是毒,人不成神终成灰。。。'}]}
Process finished with exit code 0

5.3 discriminator

在实际项目中,使用的比较少。因为这样的设计会导致系统会变的复杂。下面只是一个简答的示例。根据车辆类型编号返回不同类型的车辆特性。


  
  
  
  
  
  
  
    
    
    
    
  

6.延迟加载

有时候,我们一开始并不想取出级联的数据,如果需要时候,再取访问。

MyBatis的配置有两个全局参数lazyLoadingEnabled、aggressiveLazyLoading

或者在对应节点上加入fetchType属性。它有两个值 lazy\eager。

7.缓存

7.1 系统缓存(一级缓存和二级缓存)

MyBatis对缓存提供支持,但是在没有配置的默认情况下,它只开启一级缓存(一级缓存只是相对于同一个Sqlsession而言)

但是如果使用不同的SqlSession对象因为不同的SqlSession都是相互隔离的,所以使用相同的Mapper、参数和方法,它还是会再次发送SQL到数据库执行,返回结果。

public static void main(String[] args) {
        SqlSessionFactory factory = null;
        SqlSession sqlSession = null;
        try {
            factory = buildSqlSessionFactory();
            sqlSession = factory.openSession();
            AuthorDao authorDao = sqlSession.getMapper(AuthorDao.class);
            BlogDao blogDao = sqlSession.getMapper(BlogDao.class);
            PostDao postDao = sqlSession.getMapper(PostDao.class);
            Blog blog = blogDao.selectBlog(1);
            Blog blog2 = blogDao.selectBlog(1);
            System.out.printf(blog.toString());
            sqlSession.commit();
        } catch (Exception ex) {
            ex.printStackTrace();
            sqlSession.rollback();
        }
    }

blog2在获取数据的时候,就没有查询数据库。

7.2开启二级缓存

SqlSessionFactory层面上二级缓存是不开启的,需要进行配置。只需要在mapper映射文件加上以下配置。并且所有的POJO需要实现Serializable接口


效果

  • 1.映射语句文件中的所有 select 语句将会被缓存。
  • 2.映射语句文件中的所有 insert,update 和 delete 语句会刷新缓存。
  • 3.映射语句文件中的所有 insert,update 和 delete 语句会刷新缓存。
  • 4.根据时间表(比如 no Flush Interval,没有刷新间隔), 缓存不会以任何时间顺序 来刷新。
  • 5.缓存会存储列表集合或对象(无论查询方法返回什么)的 1024 个引用。
  • 6.缓存会被视为是 read/write(可读/可写)的缓存,意味着对象检索不是共享的,而 且可以安全地被调用者修改,而不干扰其他调用者或线程所做的潜在修改。

eviction属性

  • LRU – 最近最少使用的:移除最长时间不被使用的对象。(默认)
  • FIFO – 先进先出:按对象进入缓存的顺序来移除它们。
  • SOFT – 软引用:移除基于垃圾回收器状态和软引用规则的对象。
  • WEAK – 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。

你可能感兴趣的:(4.3 映射器(Mapper) sql、参数、resultMap、关联标签及延时加载详解)