MyBatis in Action——MyBatis剖析

MyBatis的体系结构

SqlSessionFactory
SqlSessionFactory是MyBatis的关键对象,它是单个数据库映射关系经过编译后的内存镜像。
它是创建SqlSession的工厂类。
SqlSession
SqlSession是MyBatis的关键对象,是执行持久化操作的对象,类似于JDBC中的Connection。它是应用程序与持久存储层之间执行交互操作的一个单线程对象。
它的底层封装了JDBC连接。SqlSession的实例不能被共享,也是线程不安全的,决不能将SqlSession实例的引用放在一个类的静态字段甚至是实例字段中。所以,执行完后必须关闭,也应该使用finnally来关闭它。
它有如下方法:

<T> T selectOne(String statement, Object parameter)
<E> List<E> selectList(String statement, Object parameter)
<K,V> Map<K,V> selectMap(String statement, Object parameter, String mapKey)
int insert(String statement, Object parameter)
int update(String statement, Object parameter)
int delete(String statement, Object parameter)

深入MyBatis配置文件

通过上面的介绍,我们知道MyBatis持久化操作离不开SqlSessionFactory对象,这个对象是整个数据库映射关系经过编译后的内存镜像,该对象的openSession()方法可以打开SqlSession对象,该对象由SqlSessionFactoryBuilder加载MyBatis的配置文件生成。
我们看看这句话发生了什么:SqlSessionFactory sqlSessionFactory=new SqlSessionFactoryBuilder().build(inputStream);
SqlSessionFactory根据传入的数据流生成Configuration对象,然后根据Configuration对象创建默认的SqlSessionFactory实例。

MyBatis的配置文件结构
MyBatis的配置文件包含了影响MyBatis行为的信息。文档的结构如下:
在这里插入图片描述
settings设置:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
typeAliases类型命名:
类型命名是用来减少冗余的限定名的。

<typeAliases>
	<typeAlias alias="user" type="com.leesanghyuk.domain.User"/>
</typeAliases>

这样配置,user可以用在任何使用到这个类的地方。

<typeAliases>
	<package name="com.leesanghyuk.domain"/>
</typeAliases>

这样在com.leesanghyuk.domain里的java bean,在没有注解的情况下,会使用Bean的首字母小写的非限定类名作为它的别名。比如com.leesanghyuk.domain.User的别名为user,若有注解,则别名为其注解值。

@Alias("user")
public class User{
...
}

可能不太清楚,具体请看:
https://blog.csdn.net/majinggogogo/article/details/71503263

typeHandlers类型处理器
这个标签的作用是用于处理类型的转化,比如你想把String类型的日期(如2018-11-01)转换为能够存储在数据库中Date类型,就需要转换。

详情查看:
https://blog.csdn.net/chenbaige/article/details/72568959

objectFactory对象工厂
MyBatis 每次创建结果对象的新实例时,它都会使用一个对象工厂(ObjectFactory)实例来完成。 默认的对象工厂需要做的仅仅是实例化目标类,要么通过默认构造方法,要么在参数映射存在的时候通过参数构造方法来实例化。 如果想覆盖对象工厂的默认行为,则可以通过创建自己的对象工厂来实现。对于像我这种水平的来说,完全就是一脸蒙逼,这到底有啥用?可以说这个对象工厂就是用来创建实体类的,MyBatis有一个DefaultObjectFactory默认对象工厂类,就像上面所说的默认的对象工厂需要做的仅仅是实例化目标类,要么通过默认构造方法,要么在参数映射存在的时候通过参数构造方法来实例化。它不做其他任何的处理。如果我们想在创建实例化一个目标类的时候做点啥其他的动作,可以继承DefaultObjectFactory,覆写父类方法,并在mybatis-config.xml中注册配置这个对象工厂类。

environments配置环境

mapper映射器

深入Mapper XML映射文件

select
他是用来映射查询语句的。例如:

<select id="selectUser" parameterType="int"
	resultType="hashmap">
SELECT * FROM TB_USER WHERE ID= #{id}
</select>

它还有很多属性:

  • id:被用来引用这条语句的标识符。
  • parameterType:这条语句的参数类的完全限定名或者别名。
  • resultType:从这条语句中返回的期望类型的类的完全限定名或别名。如果是集合,则使用resultType或resultMap,两者不可同时使用。
  • resultMap:同上。
  • flushCache:如果设置为true,则清空本地和二级缓存,默认为false。
  • useCache:如果设置为true,则导致本语句的结果被二级缓存。默认为true.
  • timeout:请求数据库的等待秒数,默认值为unset,依赖驱动。
  • fetchSize:每次返回的结果行数与这个设置的值相同,默认为unset。
  • statementType:值为STATEMENT/PREPARED/CALLABLE,这会让使用jdbc中的Statement、PreparedStatement、CallableStatement,默认为PREPARED。
  • resultSetType:结果集的类型,值为FORWARD_ONLY、SCROLL_SENSITIVE、SCROLL_INSENSITIVE,默认值为unset。
  • databaseId
  • resultOrdered
  • resultSets

insert、update、delete

  • useGeneratedKeys:(仅对insert和update有用)这会获得数据库内部自动生成的主键(比如MySql里的自动递增字段),默认为false。
  • keyProperty:http://bugyun.iteye.com/blog/2286145
  • resultType
  • order
  • statementType

sql

${} #{}的区别
https://blog.csdn.net/lohannes/article/details/79031435

下面来具体写代码:

<?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.leesanghyuk.mapper.UserMapper">
    <insert id="saveUser" parameterType="user"
            useGeneratedKeys="true">
        INSERT INTO TB_USER(name,sex,age) VALUES (#{name},#{sex},#{age})
    </insert>

    <select id="selectUser" parameterType="int" resultType="user">
        SELECT * FROM TB_USER WHERE id=#{id}
    </select>

    <update id="modifyUser" parameterType="user">
        UPDATE TB_USER 
        SET name=#{name},sex=#{sex},age=#{age}
        WHERE id=#{id}
    </update>
    
    <delete id="removeUser" parameterType="int">
        DELETE FROM TB_USER WHERE id=#{id}
    </delete>

</mapper>

因为每次测试都要读取mybatis-config.xml文件,根据配置文件获取SqlSessionFactoty然后又获取SqlSession,该方法比较麻烦,所以开发一个工厂类FKSqlSessionFactory。

package com.leesanghyuk.tst;

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 FKSqlSessionFactory {
    private static SqlSessionFactory sqlSessionFactory=null;
    //初始化创建SqlSessionFactory对象
    static {
        try {
            InputStream inputStream= Resources.getResourceAsStream("mybatis-config.xml");
            sqlSessionFactory=new SqlSessionFactoryBuilder().build(inputStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    //获取SqlSession对象的静态方法
    public static SqlSession getSqlSession(){
        return sqlSessionFactory.openSession();
    }
    //获取SqlSessionFactory的静态方法
    public static SqlSessionFactory getSqlSessionFactory(){
        return sqlSessionFactory;
    }
}

然后测试:

package com.leesanghyuk.tst;

import com.leesanghyuk.model.User;
import org.apache.ibatis.session.SqlSession;
import java.io.IOException;

public class MyBatisTest {
    public static void main(String[] args) throws IOException {
        SqlSession sqlSession=FKSqlSessionFactory.getSqlSession();
        User user=sqlSession.selectOne("com.leesanghyuk.mapper.UserMapper.selectUser",1);
        user.setName("LeesangHyuk");
        sqlSession.update("com.leesanghyuk.mapper.UserMapper.modifyUser",user);
        sqlSession.commit();
        sqlSession.close();
    }
}

ResultMaps
往往,我们select的对象返回是一个集合。resultMap的作用就是取出的数据换为开发者需要的对象。

我们新增一个映射语句如下:

 <select id="selectAllUsers" resultType="map">
        SELECT * FROM TB_USER
    </select>

代码如下:

public class MyBatisTest {
    public static void main(String[] args) throws IOException {
        SqlSession sqlSession=FKSqlSessionFactory.getSqlSession();
        List<Map<String,Object>> list=sqlSession.selectList("com.leesanghyuk.mapper.UserMapper.selectAllUsers");
        for (Map<String,Object> row:list){
            System.out.println(row);
        }
        sqlSession.commit();
        sqlSession.close();
    }
}

输出如下:
在这里插入图片描述
可以看到,查询语句返回的每一条数据都被封装成一个Map集合,列名作为Map集合的key,而列值作为Map的value。
虽然,可以作为Map集合作为返回,但是我们怎么获取单个的值赋值给User呢?

for (Map<String,Object> row:list){
            System.out.println(row);
            System.out.println(row.get("name"));
        }

输出如下:
在这里插入图片描述
很明显,我们这样取到的,这是单个的列以及单元的值。很不方便,我们需要另外一种方案,描述一个领域模型。

    <resultMap id="userResultMap" type="user">
        <id property="id" column="id"/>
        <result property="name" column="name"/>
        <result property="sex" column="sex"/>
        <result property="age" column="age"/>
    </resultMap>

    <select id="selectAllUsers2" resultMap="userResultMap">
        SELECT * FROM TB_USER
    </select>

上面使用了新的元素<resultMap>,属性包含id和type,很好理解。其元素如下:

  • <id>:表示数据表的主键,其中column表示列名,property表示数据库列映射到返回类型的属性。
  • <result>:表示数据表的普通列。

测试代码如下:

public class MyBatisTest {
    public static void main(String[] args) throws IOException {
        SqlSession sqlSession=FKSqlSessionFactory.getSqlSession();
        List<User> userList=sqlSession.selectList("com.leesanghyuk.mapper.UserMapper.selectAllUsers2");
        for (User user: userList) {
            System.out.println(user.getName());
        }
        sqlSession.commit();
        sqlSession.close();
    }
}

这种映射,即使列名和User对象的属性名不一致,数据依然会被正确封装。
在实际开发中,还有更复杂的情况。例如,执行的是一个多表查询语句,而返回的对象关联到另一个对象,此时,简单的映射已经无法解决问题,就必须使用<resultMap>来完成映射。

我们来看看如下的例子:
我们重新创建两个表:TB_CLAZZ和TB_STUDENT

use mybatis;
create table `tb_clazz`(
	`id` int primary key auto_increment,
 `code` varchar(18)
);
insert into tb_clazz(code) values('j1601');
insert into tb_clazz(code) values('j1602');

create table `tb_student`(
	`id` int primary key auto_increment,
	`name` varchar(18),
	`sex` char(3),
	`age` int,
	`clazz_id` int,
	FOREIGN KEY (`clazz_id`) REFERENCES tb_clazz(`id`)
);
insert into tb_student(name,sex,age,clazz_id) values('java','男',22,1);
insert into tb_student(name,sex,age,clazz_id) values('json','女',18,1);
insert into tb_student(name,sex,age,clazz_id) values('c#','男',25,2);
insert into tb_student(name,sex,age,clazz_id) values('mybatis','女',20,2);

好了,以上的SQL语句插入了两个班级记录和4个学生记录,包含了外键。
现在创建一个Clazz对象和Student对象分别映射两表。

public class Clazz {
    private Integer id;
    private String code;
    ...
}
 public class Student {
    private Integer id;
    private String name;
    private String sex;
    private Integer age;
    //关联的Clazz
    private Clazz clazz;
    ...
}

需要注意的是,这里使用的是Student中的Clazz对象并不是使用id对应的。

现在写一个新的StudentMapper.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.leesanghyuk.mapper.StudentMapper">
    <resultMap id="studentResultMap" type="Student">
        <id property="id" column="id"/>
        <result property="name" column="name"/>
        <result property="sex" column="sex"/>
        <result property="age" column="age"/>
        <!--关联映射-->
        <association property="clazz" column="clazz_id" 
                     javaType="Clazz" select="selectClazzWithId"/>
    </resultMap>
    
    <select id="selectClazzWithId" resultType="Clazz">
        SELECT * FROM TB_CLAZZ WHERE id=#{id}
    </select>
    <select id="selectStudent" resultMap="studentResultMap">
        SELECT * FROM TB_STUDENT
    </select>

</mapper>

我们在mybatis-config里面加入新的typeAliases以及mapper即可:

	...
   <!--typeAliases-->
    <typeAliases>
        <typeAlias type="com.leesanghyuk.model.User" alias="user"/>
        <package name="com.leesanghyuk.model"/>
    </typeAliases>
    ...
      <mappers>
        <mapper resource="com/leesanghyuk/mapper/UserMapper.xml"/>
        <mapper resource="com/leesanghyuk/mapper/StudentMapper.xml"/>
    </mappers>

然后写个测试即可:

package com.leesanghyuk.tst;

import com.leesanghyuk.model.Student;
import org.apache.ibatis.session.SqlSession;

import java.util.List;

public class SelectStudentTest {
    public static void main(String[] args) {
        SqlSession sqlSession=FKSqlSessionFactory.getSqlSession();
        List<Student> students=sqlSession.selectList("com.leesanghyuk.mapper.StudentMapper.selectStudent") ;
        for (Student s : students) {
            System.out.println(s.getName());
        }
        sqlSession.commit();
        sqlSession.close();
    }
}

ojbk~
我们来了解一下<association>,除去之前解释过的columnproperty两个属性,还有:

  • javaType:也就是该属性对应的类型名称。
  • select:表示执行一条查询语句,并将查询到的数据封装到property所代表的类型对象当中。

结果如下:
在这里插入图片描述
新的难题出现了,假如我们是要打印班级的信息而不是学生的信息怎么办?我们知道,一个班级中会有多个学生存在,而一个学生只属于一个班级。但是我们的表tb_clazz并不存在外键映射到tb_student。
我们给Clazz.java新增一个字段:

public class Clazz {
    private Integer id;
    private String code;
    private List<Student> students;
    ...
}

现在配置StudentMapper.xml

 <!--映射班级对象的resultMap-->
    <resultMap id="clazzResultMap" type="Clazz">
        <id property="id" column="id"/>
        <result property="code" column="code"/>
        <collection property="students" column="id"
                    javaType="ArrayList" ofType="Student"
                    select="selectStudentWithId"/>
    </resultMap>
    
    <select id="selectStudentWithId" resultType="Student">
        SELECT * FROM TB_STUDENT WHERE clazz_id=#{id}
    </select>
    <select id="selectClazz" resultMap="clazzResultMap">
        SELECT * FROM TB_CLAZZ
    </select>

首先来解释一下执行id为selectClazz的语句,因为Clazz不是一个简单的对象,它其中还包含了学生的集合对象,所以使用resultMap去映射返回类型。
<collection>的元素解释如下:

  • property:表示返回类Clazz对象的属性students
  • column:表示使用这个列作为参数进行之后的select语句查询。
  • javaType:表示该属性对应的类型名称。
  • ofType:表示集合当中的类型,即集合为ArrayList<Student>
  • select:要执行的语句id

下面测试:

package com.leesanghyuk.tst;

import com.leesanghyuk.model.Clazz;
import com.leesanghyuk.model.Student;
import org.apache.ibatis.session.SqlSession;

import java.util.List;

public class SelectClazzTest {
    public static void main(String[] args) {
        SqlSession sqlSession=FKSqlSessionFactory.getSqlSession();
        List<Clazz> clazzes=sqlSession.selectList("com.leesanghyuk.mapper.StudentMapper.selectClazz");
        for (Clazz c:clazzes) {
            System.out.println(c);
            for (Student s:c.getStudents()) {
                System.out.print(s.getName()+" "+c.getCode()+" ");
            }
            System.out.println();
        }
        sqlSession.commit();
        sqlSession.close();
    }
}

注意,由于这里Student又包含了Clazz,所以搜索出来的Student的Clazz属性是空。这个问题怎么解决呢?我们下一章继续讨论。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值