Mybaits系列之MyBatis的发展之路,怎么用好MyBatis

为什么要用Mybatis?

先看看原生操作JDBC的步骤

*注册驱动,获取连接

*创建StateMent对象

*execute()方法执行SQL

*把结果集转换成POJO

*关闭资源

一看,存在大量的重复代码,繁琐过程,结果集的处理很复杂,数据库连接的管理也很麻烦。

所以,慢慢就出现了一些包装数据库操作的框架,springJDBC、dbUtils、heibernate、mybatis等。

先说说比较早的springJDBC和dbUtil,它们主要解决了数据源的封装和映射结果集的包装。

但是并没有真正解决SQL语句的硬编码(直接把sql写在代码中,业务操作和数据库操作耦合在一期);参数只能按顺序传入(占位符),不能就通过一个对象传进去来操作;没有实现实体类到数据库记录的映射;没有提供缓存等功能。

为了解决这些问题,真正的ORM级的框架就出现了。

Mybaits系列之MyBatis的发展之路,怎么用好MyBatis_第1张图片

Hibernate(全自动化,jpa是对hibernate的封装):

hbm.xml或者实体类注解(class对应表,字段对应列)

提供session对象来直接操作对象,比如session.save(bo) 

问题:

1、不能指定部分字段(不能像sql样只查部分)

2、无法自定义SQL,优化困难

3、不支持动态SQL

MyBatis(半自动化):

1、使用连接池对连接进行管理

2、SQL和代码分离,集中管理

3、参数映射和动态和动态SQL

4、结果集映射

5、缓存管理

6、重复SQL的提取,sql的标签,可以在其它地方来引用

7、插件机制

 

实际开发建议:

*业务简单的项目可以使用Hibernate

*需要灵活的SQL,可以使用,MyBatis

*对性能要求高,可以使用JDBC

*Spring JDBC可以和ORM框架混用

 

MyBatis编程式开发核心配置

去除Spring容器,我们就用java和Mybatis来耦合,看看纯MyBatis是怎么编程的。

1、mybatis和MySQL.jar包依赖

2、全局配置文件mybatis-config.xml

3、映射器Mapper.xml

4、Mapper接口

  /**
     * 使用MyBatis API方式
     * @throws IOException
     */
    @Test
    public void testStatement() throws IOException {
        String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

        SqlSession session = sqlSessionFactory.openSession();
        try {
            Blog blog = (Blog) session.selectOne("com.darker.mapper.BlogMapper.selectBlogById", 1);
            System.out.println(blog);
        } finally {
            session.close();
        }
    }

/**
     * 通过 SqlSession.getMapper(XXXMapper.class)  接口方式
     * @throws IOException
     */
    @Test
    public void testSelect() throws IOException {
        String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

        SqlSession session = sqlSessionFactory.openSession(); // ExecutorType.BATCH
        try {
            BlogMapper mapper = session.getMapper(BlogMapper.class);
            Blog blog = mapper.selectBlogById(1);
            System.out.println(blog);
        } finally {
            session.close();
        }
    }

 

四个核心对象的生命周期

SqlSessionFactoryBuiler  创建工厂类(产生了工厂类对象就可以丢弃,方法内部)

SqlSessionFactory   创建会话(需要一直存在,不停的创建会话,应该是单例一直存在的)

SqlSession  会话(一个请求的方法里面存在)

Mapper映射(单个方法)

Mybaits系列之MyBatis的发展之路,怎么用好MyBatis_第2张图片

 

mybatis-config.xml(全局配置文件详解)

首先看到我们的xml,发现有个root的标签configuration,既然有这个根标签,马上就能想到MyBatis中是不是会有个Configruation配置类。

Mybaits系列之MyBatis的发展之路,怎么用好MyBatis_第3张图片

Mybaits系列之MyBatis的发展之路,怎么用好MyBatis_第4张图片

发现了,果然有这个配置类,所以一直说配置源于代码,比如spring的各种配置一样也能在源码中找到(好多小伙伴一直感觉各个版本的配置标签不统一,可能会有更改,不好查询,其实直接看源码就可以),下面我们就来好好分析下这些标签。

一级标签:

*properties,把一些需要重复引用的属性放到这个标签中, 比如数据库连接得配置。

*settings,mybatis核心行为得控制。

Mybaits系列之MyBatis的发展之路,怎么用好MyBatis_第5张图片

 *typeAliases别名配置,简化类型全路径得拼写(一般会换成包扫描)

Mybaits系列之MyBatis的发展之路,怎么用好MyBatis_第6张图片

*typeHandler,类型处理器,为什么mybatis可以把数据库得varchar类型对应到我们java的string类型

Mybaits系列之MyBatis的发展之路,怎么用好MyBatis_第7张图片

Mybaits系列之MyBatis的发展之路,怎么用好MyBatis_第8张图片

它也支持自定义


public class MyTypeHandler extends BaseTypeHandler {
    public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType)
            throws SQLException {
        // 设置 String 类型的参数的时候调用,Java类型到JDBC类型
        // 注意只有在字段上添加typeHandler属性才会生效
        // insertBlog name字段
        System.out.println("---------------setNonNullParameter1:"+parameter);
        ps.setString(i, parameter);
    }

    public String getNullableResult(ResultSet rs, String columnName) throws SQLException {
        // 根据列名获取 String 类型的参数的时候调用,JDBC类型到java类型
        // 注意只有在字段上添加typeHandler属性才会生效
        System.out.println("---------------getNullableResult1:"+columnName);
        return rs.getString(columnName);
    }

    public String getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
        // 根据下标获取 String 类型的参数的时候调用
        System.out.println("---------------getNullableResult2:"+columnIndex);
        return rs.getString(columnIndex);
    }

    public String getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
        System.out.println("---------------getNullableResult3:");
        return cs.getString(columnIndex);
    }
}

*objectFactory,对象工厂,通过反射创建java对象。

Mybaits系列之MyBatis的发展之路,怎么用好MyBatis_第9张图片

*plugin,允许你在映射语句执行过程中的某一点进行拦截调用。

*environments,配置环境以及里面的熟悉,数据源,事务等。

Mybaits系列之MyBatis的发展之路,怎么用好MyBatis_第10张图片

*mappers,映射器,定义 SQL 映射语句。

 

Mapper中内含的八大标签

,提取重复被使用的sql语句。

select FROM tbl_dept e where 1=1 and d_id = #{did,jdbcType=INTEGER}

choose(when,otherwise)标签的使用

 

 trim(where,set)标签的使用

 
    
        insert into tbl_emp
        
            
                emp_id,
            
            
                emp_name,
            
            
                gender,
            
            
                email,
            
            
                d_id,
            
        
        
            
                #{empId,jdbcType=INTEGER},
            
            
                #{empName,jdbcType=VARCHAR},
            
            
                #{gender,jdbcType=CHAR},
            
            
                #{email,jdbcType=VARCHAR},
            
            
                #{dId,jdbcType=INTEGER},
            
        
    

foeach 标签的使用,批量操作比java层面节省性能


    
        delete from tbl_emp where emp_id in
        
            #{item.empId,jdbcType=VARCHAR}
        
    

 

 批量操作执行器

上面动态标签foreach可以批量操作,但如果出现要批量删除或者新增非常大量的数据,就会导致这个sql可能会非常长,而MyBatis对这种sql的大小是有限制的,默认是4m,那么这种情况应该怎么解决呢。

这里就需要用到MyBatis里面提供的批量操作执行器,在xml中的标签可以配置

simple:默认,对应Statement。

reuse:可重用的执行器,就是会把sql缓存起来,第二次用就会再去拿到,对应PrepareStatement。

batch:可重用,且还会执行批量操作,依然是对应PrepareStatement。

在JDBC代码层面对应

   /**
     * 原生JDBC的批量操作方式 ps.addBatch()
     * @throws IOException
     */
    @Test
    public void testJdbcBatch() throws IOException {
        Connection conn = null;
        PreparedStatement ps = null;

        try {
            // 注册 JDBC 驱动
            Class.forName("com.mysql.jdbc.Driver");

            // 打开连接
            conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/gp-mybatis?useUnicode=true&characterEncoding=utf-8&rewriteBatchedStatements=true", "root", "123456");
            ps = conn.prepareStatement(
                    "INSERT into blog values (?, ?, ?)");

            for (int i = 1000; i < 101000; i++) {
                Blog blog = new Blog();
                ps.setInt(1, i);
                ps.setString(2, String.valueOf(i));
                ps.setInt(3, 1001);
                ps.addBatch();
            }

            ps.executeBatch();
            // conn.commit();
            ps.close();
            conn.close();
        } catch (SQLException se) {
            se.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (ps != null) ps.close();
            } catch (SQLException se2) {
            }
            try {
                if (conn != null) conn.close();
            } catch (SQLException se) {
                se.printStackTrace();
            }
        }
    }

 

嵌套(关联)查询及问题 

记得用Hibernate的时候,可能一个实体类中会用到其它实体类的属性,那么一般会在那个实体类中加字段或者集合来映射,但这样做却等于污染了实体类。

那如果我们不想污染实体类,MyBatis是怎么操作的呢,举个例子,首先有两个实体类。

public class Author implements Serializable {
    Integer authorId; // 作者ID
    String authorName; // 作者名称

    public Integer getAuthorId() {
        return authorId;
    }

    public void setAuthorId(Integer authorId) {
        this.authorId = authorId;
    }

    public String getAuthorName() {
        return authorName;
    }

    public void setAuthorName(String authorName) {
        this.authorName = authorName;
    }
}

public class Blog implements Serializable{
    Integer bid; // 文章ID
    String name; // 文章标题
    Integer authorId; // 文章作者ID

    public Integer getBid() {
        return bid;
    }

    public void setBid(Integer bid) {
        this.bid = bid;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAuthorId() {
        return authorId;
    }

    public void setAuthorId(Integer authorId) {
        this.authorId = authorId;
    }

    @Override
    public String toString() {
        return "Blog{" +
                "bid=" + bid +
                ", name='" + name + '\'' +
                ", authorId='" + authorId + '\'' +
                '}';
    }
}

第一种方式:嵌套结果

先建一个包装对象,然后把结果返回这个包装对象中,里面包含了作者实体类,且在resultMap中用association定义了作者实体类的全部属性,这样就等于直接把结果放到了resultMap中。

public class BlogAndAuthor implements Serializable {
    Integer bid; // 文章ID
    String name; // 文章标题
    Author author; // 作者

    public Integer getBid() {
        return bid;
    }

    public void setBid(Integer bid) {
        this.bid = bid;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Author getAuthor() {
        return author;
    }

    public void setAuthor(Author author) {
        this.author = author;
    }

    @Override
    public String toString() {
        return "BlogAndAuthor{" +
                "bid=" + bid +
                ", name='" + name + '\'' +
                ", author=" + author +
                '}';
    }
}
 
    
        
        
        
        
            
            
        
    

 


    

第二种方式:嵌套查询

依然使用上面的包装类,但是resultMap中不直接定义全部的属性,而是用association定义一个select的查询器,也就是说当用到这个查询的时候,会去触发这个select方法。

  
    
        
        
         
    

 

 
    

    
    

在这种方式里面就存在一个N+1的问题,举个例子,当我们查询这个列表的时候,可能我只是需要显示博客的内容,并不需要一开始就显示作者,但是它依然会把每个selectAuthor方法走一遍,这样就等于每次查询都要查两个SQL,显然是不利于效率的,如果不调用这个属性的话,我们并不希望它多查一次SQL。 

解决办法当然也是有的,那就是开启延迟加载,上面早前说到的全局配置中settings,里面有个懒加载开关。

这里用代码调用来说明下区别:

当我关闭了延迟加载的时候,使用这个方法

    /**
     * 一对一,一篇文章对应一个作者
     * 嵌套查询,会有N+1的问题
     */
    @Test
    public void testSelectBlogWithAuthorQuery() throws IOException {
        String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        SqlSession session = sqlSessionFactory.openSession();
        BlogMapper mapper = session.getMapper(BlogMapper.class);

        BlogAndAuthor blog = mapper.selectBlogWithAuthorQuery(1);
        System.out.println("-----------:"+blog.getClass());
        // 如果开启了延迟加载,会在使用的时候才发出SQL
        // equals,clone,hashCode,toString也会触发延迟加载
        // System.out.println("-----------调用toString方法:"+blog);
        //System.out.println("-----------getAuthor:"+blog.getAuthor().toString());
        // 如果 aggressiveLazyLoading = true ,也会触发加载,否则不会
        //System.out.println("-----------getName:"+blog.getName());
    }

 Mybaits系列之MyBatis的发展之路,怎么用好MyBatis_第11张图片

Mybaits系列之MyBatis的发展之路,怎么用好MyBatis_第12张图片

得出的答案你会发现,我并没有调用这个实体类中的作者,但它依然执行了selectAuthor这套SQL语句。

当我开启延迟加载的时候,使用这个方法 ,你会发现它只执行了一条sql。

Mybaits系列之MyBatis的发展之路,怎么用好MyBatis_第13张图片

 当我再次在方法中调用这个对象getAuthor方法的时候,它才会去执行下一条SQL,也就是说只要用到的时候才去查询。

Mybaits系列之MyBatis的发展之路,怎么用好MyBatis_第14张图片 

当你开了全局延迟加载以后,你在某个地方又不想要延迟加载怎么办呢?

你可以在你想要的方法上加上aggressiveLazyLoading属性,默认关闭,可以用过select标签中的fetchtype来覆盖。

延迟加载的原理

显然能增强类的功能,使得每次只有getAuthor方法的时候才去调用SQL,那么原理只能是它改造了我们的getAuthor方法,能做到这个的只能是代理了。

那么通过debug你可以看到,你的对象的class其实变了。

变成了一个javassist代理对象。

Mybatis可以支持两种代理方式,一种是默认的java方法,一种是cglib方式,可以在配置种设置。 

 

MyBatis的分页问题

逻辑分页(假分页)和物理分页(真分页,使用数据库支持的语法)

逻辑分页(假分页):

Mybatis提供对象RowBounds

Mybaits系列之MyBatis的发展之路,怎么用好MyBatis_第15张图片

 /**
     * 逻辑分页
     * @throws IOException
     */
    @Test
    public void testSelectByRowBounds() throws IOException {
        String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

        SqlSession session = sqlSessionFactory.openSession();
        try {
            BlogMapper mapper = session.getMapper(BlogMapper.class);
            int start = 0; // offset
            int pageSize = 5; // limit
            RowBounds rb = new RowBounds(start, pageSize);
            List list = mapper.selectBlogList(rb); // 使用逻辑分页
            for(Blog b :list){
                System.out.println(b);
            }
        } finally {
            session.close();
        }
    }

Mybaits系列之MyBatis的发展之路,怎么用好MyBatis_第16张图片

Mybaits系列之MyBatis的发展之路,怎么用好MyBatis_第17张图片

按数据库和sql来说,必然式应该查出6条,但我传了个 RowBounds对象后,却完成了逻辑分页,那么它是怎么做到的呢,它会先把数据全部放到内存中,然后定位到偏移量offset,也就是起始值,把之前的全部去掉,然后在定位到limit也就是结尾值,然后把这部分取出来,所以,这样是非常浪费性能的。

物理分页(真分页)

最原始的方法,java层面计算分页点传入

插件形式,自动帮我们生成物理分页数据,pagehelper 

 

逆向工程

MaBatis-generator,自动生成映射bo,已经一些通用的curd

问题:字段表发生了变化怎么办?

思路:

(1)、继承生成的mapper文件(好维护但多了两个文件,一个继承的接口文件和一个XML)

/**
 *
 * 扩展类继承了MBG生成的接口和Statement
 *
 */
public interface BlogMapperExt extends BlogMapper {
    /**
     * 根据名称查询文章
     * @param name
     * @return
     */
    public Blog selectBlogByName(String name);
}

(2)通用Mapper(tk mybatis,mybatisplus) 

通用curd

条件构造

代码生成

翻页

批量操作

ps:

附上参考网站MyBatis中文网

MyBatis中文网 

你可能感兴趣的:(Mybaits系列之MyBatis的发展之路,怎么用好MyBatis)