Mybatis

Mybatis笔记连载下篇连接MyBatis缓存

一、搭建demo

在使用Mybatis读取数据库时,需要构建SqlSessionFactory对象,获取这个对象也非常简单,使用Resources的工具类中的getResourceAsReader方法读取xml配置,再通过SqlSessionFactoryBuilder来来创创建一个SqlSessionFacotry对象。

然后我们需要获取SqlSession,他的作用是可以直接执行已映射的SQL语句。

使用的数据表和其映射类都为(spring)Dao中的使用过的。继上篇笔记Dao中所有配置。

USE test;
DROP TABLE IF EXISTS `t_user`;
CREATE TABLE `t_user` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(45) DEFAULT NULL,
  `age` int(11) DEFAULT NULL,
  `money` decimal(10,2) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

INSERT INTO `t_user` VALUES ('1', '张三', '24', '666.66');
INSERT INTO `t_user` VALUES ('2', '李四', '25', '888.88');
INSERT INTO `t_user` VALUES ('3', '王二', '26', '999.99');
INSERT INTO `t_user` VALUES ('4', '小明', '27', '555.55');
INSERT INTO `t_user` VALUES ('5', '小赵', '28', '333.33');

定义User类

public class User {
    @Override
    public String toString(){
        return "Id:"+this.getId()+" Name:"+this.getName()
                +" Age:"+this.getAge()+" Money:"+this.getMoney();
    }
    private int Id;
    private String Name;
    private int Age;
    private double Money;
    public User(){}
    public User(String name,int age,double money){
        Name = name;
        Age = age;
        Money = money;
    }
    public int getId() {
        return Id;
    }
    public void setId(int id) {
        Id = id;
    }
    public String getName() {
        return Name;
    }
    public void setName(String name) {
        Name = name;
    }
    public int getAge() {
        return Age;
    }
    public void setAge(int age) {
        Age = age;
    }
    public double getMoney() {
        return Money;
    }
    public void setMoney(double money) {
        Money = money;
    }
}

先引入MyBatis对应的依赖,若已有mysql依赖可不用再增加。

<!--    mysql-->
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>5.1.6</version>
    </dependency>
<!--    mybatis-->
<dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis</artifactId>
      <version>3.4.6</version>
    </dependency>

构建xml配置文件,主要包含两个配置文件,一个是映射配置文件,一个是Mybatis配置文件。配置具体含义可以不再研究,先搭建能运行的程序。

新建UserMapper.xml,配置的是sql语句与实他出类的映射。namespace的属性可以随意设置一个不存在的类名。但在执行映射语句时,必须与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">
<!--namespace随意取-->
<mapper namespace="test.mybatis.DBMapping.UserMapper">
    <select id="getUserList" resultType="test.DAO.User">
        select * from t_user
    </select>
</mapper>

新建mybatis-config.xml,配置的是数据库的连接信息以及sql的mappers。

<?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">
        <environment id="development">
            <transactionManager type="JDBC"></transactionManager>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://127.0.0.1:3306/test"/>
                <property name="username" value="root"/>
                <property name="password" value="951753"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="Mybatis/UserMapper.xml"/>
    </mappers>
</configuration>

测试程序,通过配置文件构建SqlSessionFactory,获取到能执行映射文件中sql的sqlsession,然后执行SQL输出t_user中的所有用户信息。

public class BasicDemo {
    public static void main(String [] args) throws IOException {
        String resource = "Mybatis/mybatis-config.xml";
        Reader reader = Resources.getResourceAsReader(resource);
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
        SqlSession session = sqlSessionFactory.openSession();
        String stat = "test.mybatis.DBMapping.UserMapper.getUserList";
        List<User> users = session.selectList(stat);
        for(User u:users){
            System.out.println(u.toString());
        }
    }
}

执行结果当然就是输出数据库中的表的所有数据了。

使用properties文件替换配置

新建databases.properties

driver=com.mysql.jdbc.Driver
url=jdbc:mysql://127.0.0.1:3306/test

将mybatis-config.xml修改,使用properties标签引入外部配置或者使用property在配置中设置属性,然后使用${}来引用properties文件中的属性来代替使用具体的值。这样的好处是减少后续维护修改代码的范围。

<configuration>
    <properties resource="Mybatis/database.properties">
	<property name="username" value="root" />
	<property name="password" value="951753 />
    </properties>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"></transactionManager>
            <dataSource type="POOLED">
                <property name="driver" value="${driver}"/>
                <property name="url" value="${url}"/>
                <property name="username" value="${username}"/>
                <property name="password" value="${password}"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="Mybatis/UserMapper.xml"/>
    </mappers>
</configuration>

如果在多处进行了配置,那么Mybatis将按照下面的顺序来加载:

  1. 在properties元素体内指定的属性首先被读取。
  2. 在properties中的resource属性读取路径下的配置文件会覆盖已指定的同名属性。
  3. 读取作为方法参数传递的属性,并覆盖已读取的同名属性。

就是说,方法参数传递具有最高优先级,resource指定配置文件中次之,最低级是propertis中定义的属性。

使用typeAliase

类型别名为java类型设置一个短的名字。在本文的demo中使用的UserMapper.xml文件中,设置的resultType时用的是User类的完全限定名。

使用typeAlias标签可以设置test.DAO.User的别名,注意在别名中的alias属性不分大小写。即以下的设置resultType中使用user和USER都可以。

  <typeAliases>
        <typeAlias type="test.DAO.User" alias="User"/>
        <package name="test.DAO"/>
    </typeAliases>

然后将UserMapper.xml中的resultType属性更改为user。

<mapper namespace="test.mybatis.DBMapping.UserMapper">
    <select id="getUserList" resultType="user">
        select * from t_user
    </select>
</mapper>

第二种用法,如果当你的映射类比较多,你可以选择直接指定一个包名,用package来直接搜索。在没有注解的情况下,会使用这个类的类名来作为他的别名,将mybatis.xml中的typeAliase属性更改为。这时UserMapper中的resultType仍然会起作用。

<typeAliases>
    <package name="test.DAO"/>
</typeAliases>

不过需要注意的一点是:typeAliases标签必须放在environments之前,不然会标签报错。

还有使用注解的用法,则别名为其注解值。像下面代码中,将注解放在类的上方标注@Alias(),在resultType中就可以直接使用users别名。

@Alias("users")
public class User {

}

二、XML配置

在demo中的mybatis-config.xml中,包含了environments配置环境元素,而所有的配置都是在这个元素里完成的,environment是environments的子元素,可配置多个,environments中通过default属性指定默认环境配置,在mybatis-config.xml中即将一个environments的子元素environment的id设为development并在enviroments中设定默认环境配置为development。在environment中又包含两个子元素:事务管理器(tansactionManager)和数据源(dataSource)。

1、事务管理器(transactionManager)

在MyBatis中有了类型的事务管理器type="JDBC|MANAGED"

  • JDBC:这个配置就是直接使用了JDBC的提交和回滚设置,它依赖于从数据源得到的连接来管理事务作用域。我们使用的就是JDBC类型。
  • MANAGED:这个配置让容器来管理事务的整个生命周期,默认情况下,它会关闭连接。如果不希望关闭,则需要将closeConnection属性设置为false来阻止这种关闭行为。

2、数据源(dataSource)

dataSource元素使用JDBC数据源来配置JDBC连接对象的资源。

属性type="UNPOOLED|POOLED|JNDI"

  • UNPOOLED:这个type实现只是每次被请求时打开和关闭连接,并没有实现连接池,对于某些数据库来说,使用连接池并不重要,这个配置就很适合这种情形。UNPOOLED有以下5种属性。
属性描述
driverJDBC驱动完全限定名
url数据库的JDBC URL地址
username登录数据库的用户名
password登录数据库的密码
defaultTransactionIsolationLevel默认的连接事务隔离级别
  • POOLED:除了拥有UNPOOLED的属性外,他还实现了“池”的概念,可以省去很多连接创建的时间。
属性描述
poolMaximunActiveConnections在任意时间可以存在的活动,最大连接数
poolMaximumIdleConnections任意时间可能存在的空闲连接数
poolMaximumCheckoutTime被强制返回之前,池中连接被检出时间,默认20000
poolTimeToWait连接最少等待时间,超过即尝试重新获得一个新连接
poolMaximumLocalBadConnectionTolerance重新获取连接的最大尝试次数,默认为3
poolPingQuery使用某个查询来检测数据库是否正常工作,默认为NO PING QUERY SET
poolPingEnabled是否启动检测数据库侦测,如果开启则需要在poolPingQuery设置一个查询语句
poolPingConnectionsNotUsedFor配置poolPingQuery的侦测频率,默认为0,无时无刻都在侦测
  • JNDI:这个数据源的实现是为了能在EJB或应用服务器这类容器中使用,容器可以集中或在外部配置数据源,然后防止一个JNDI上下文的引用。
属性描述
initial_context用来在InitialContext中寻找上下文。这是一个可选属性,如果忽略,那么data_source属性将会直接从InitialContext中寻找
data_source引用数据源实例位置的上下文的路径。提供了inital_context配置时会在其返回的上下文中进行查找,没有提供时则直接在InitialContext中查找

3、映射器(mappers)

在mybatis-config.xml中最后一个元素,mappers,这是实体类与数据库表的纽带,该元素中包含若干个mapper子元素,子元素告诉mapper到哪里去找映射文件,这里映射文件可以相对于类路径的资源引用,也可以使用完全限定资源定位符(file:///的url),或类名和包名。在这里引用的是UserMapping.xml,属于相当于类路径的资源引用。

使用mappers映射器配置会告诉Mybatis去哪里找映射文件,而我们就应该再进行每个SQL映射文件的实现了。

三、XML映射文件

理解完mybatis-config.xml,我们要看MyBatis中的映射语句,这些语句都是在mapper元素中的。mapper中的顶级元素有:cache、cacher-ref、resultMap、sql、insert、update、delete、select。

1、查询元素select

在例子中的UserMapper.xml中使用到了select元素。

<select id="getUserList" resultType="user">
        select * from t_user
    </select>

id属性为这个语句的唯一标识符,用来引用这条语句,resultType为期望返回的类。大概意思就是你可以通过getUserList这个语句名字来使用select * from t_user这条sql语句,并且返回的结果集装载在user中,而这里的user是在mybatis-config.xml中起的别名。

可以这样设置来通过id来查询t_user。

   <select id="getOneUserList" resultType="user">
        select * from t_user where id=#{id}
    </select>

以下就是使用这个语句来查询id为1的程序语句。

stat = "test.mybatis.DBMapping.UserMapper.getOneUserList";
User user = session.selectOne(stat,1);
System.out.println(user.toString());

select元素中的可配置的属性列表如下。

属性描述
id在命名空间中的唯一标识,用来引用这条语句
parameterType将要传入这条语句的参数类的完全限定名或别名。默认为unset
resultType将要返回去期望类型的类
resultMap外部resultMap的引用。
flushCache若设置为true,则语句被调用时,都会清空本地缓存和二级缓存,默认为false
useCache若设置为true,将会导致本条语句的结果被二级,。默认是true
timeout等待返回请求结果的最长时长。默认为unset
fetchSize尝试影响驱动程序每次批量返回的结果行数和这个设置值相等,默认为unset
statementTypeSTATEMENT,PREPARED,CALLABLE,默认为PREPARED
resultSetTypeFORWARD_ONLY,SCROLL_SENSITIVE,SCROLL_INSENSITIVE,默认unset
databaseId如果配置了databaseIdProvide,会加载所有不带databaseId或匹配当前databaseId的语句;如果带或不带的语句都有,则不带的会被忽略
resultOrdered这个设置仅针对嵌套结果select语句适用:如果为true,就是假设包含了嵌套结果集或是分组了,这样的和当返回一个主结果行的时候就不会发生对前面结果集的引用情况,这就使用在获取嵌套的结果集的时候不至于导致内存不足,默认为false
resultSets这个设置仅对多结果集的情况适用,它将列出语句执行后返回的结果集并为每个结果集给一个名称,名称是逗号分隔的

2、更新元素insert、update、delete

这三个元素都是更新操作,实现也和select的比较相似。

insert、update、delete三个元素对应的属性如下

属性描述
id在命名空间中的唯一标识,用来引用这条语句
parameterType将要传入这条语句的参数类的完全限定名或别名。默认为unset
flushCache若设置为true,则语句被调用时,都会清空本地缓存和二级缓存,默认为false
timeout等待返回请求结果的最长时长。默认为unset
statementTypeSTATEMENT,PREPARED,CALLABLE,默认为PREPARED
useGeneratedKeys仅对insert和update有用,这会令Mybatis使用JDBC的getGeneratedKeys方法来取出由数据库内部生成的主键。默认为false
keyProperty仅对insert和update有用,唯一标记一个属性,Mybatis会通过getGeneratedKeys的返回值或通过insert语句的selectKey子元素设置它的键值,默认为unset,可用逗号分隔多个属性名称列表
keyColumn仅对insert和update有用,通过生成的键值设置表中的列名,这个设置仅在某些数据库是必须的,当主键列不是表中的第一列的时候需要设置。如果希望得到多个生成的列,可以是逗号分隔的属性名称列表
databaseId如果配置了databaseIdProvide,会加载所有不带databaseId或匹配当前databaseId的语句;如果带或不带的语句都有,则不带的会被忽略

在UserMapping.xml中的mapper元素下增加下列xml语句。即表示调用addUser时执行插入语句insert into t_user(name,age,money) values(#,#,#)并获取对应的自增ID。

 <insert id="addUser" parameterType="user">
        <selectKey resultType="java.lang.Integer" order="AFTER" keyProperty="id">
            select LAST_INSERT_ID()
        </selectKey>
        insert into t_user(name,age,money) values(#{name},#{age},#{money})
    </insert>

在执行insert时,不能忘记执行session.commit()否则能得到自增ID但数据库中并不存在数据。

stat = "test.mybatis.DBMapping.UserMapper.addUser";
        user = new User("mybatis",29,111.11);
        session.insert(stat,user);session.commit();
        System.out.println("新增ID:"+user.getId());

在上面使用的selectKey元素老获取插入数据的自增主键。下面是selectKey元素的主要属性

属性描述
id在命名空间中的唯一标识,用来引用这条语句
parameterType将要传入这条语句的参数类的完全限定名或别名。默认为unset
resultType将要返回去期望类型的类
resultMap外部resultMap的引用。
flushCache若设置为true,则语句被调用时,都会清空本地缓存和二级缓存,默认为false
useCache若设置为true,将会导致本条语句的结果被二级,。默认是true

3、使用可重用语句块sql

sql元素可以被用来定义可重用的sql代码段,就跟java程序中的可重用方法一样,可以包含在其他语句中。有时sql是有一些是重复的语句块,可以使用sql元素来实现SQL语句块的复用。

下面是使用原sql语句,作用是将t_user使用别名t1输出所有的字段id,name,age,money的所有行。

select t1.id,t1.name,t1.age,ti.money
from t_user as t1

我们要使用可重用语句块sql来使某些可重用的部分聚成一块。用块来拼接语句,如下。

    <!--定义选择字段块-->
    <sql id="userColumns" >
        ${alias}.id,${alias}.name,${alias}.age,${alias}.money
    </sql>
    <!--定义使用别名块-->
    <sql id="sometable">
        ${prefix} as ${alias}
    </sql>
    <!--定义使用来自表块-->
    <sql id="someinclude">
        from
        <include refid="${include_target}"></include>
    </sql>
    <!--正式使用sql拼接-->
    <select id="getUserList2" resultType="user">
        select
        <include refid="userColumns">
            <property name="alias" value="t1"/>
        </include>
        <include refid="someinclude">
            <property name="prefix" value="t_user"/>
            <property name="alias" value="t1"/>
            <property name="include_target" value="sometable"/>
        </include>
    </select>

很简单的例子,先在select前定义需要使用的sql语句块,里面可带参数,参数名id是标识,在select元素中的include元素中使用refid来引用该语句。

sql元素下使用${}中后,可以使用include下的property来定义这个变量。这里使用的例子拆分过细,在正常使用中不需要拆分过细。

4、数据集映射resultMap

在使用select元素中有两种使用返回类型的设置,一种是resultType,一种是resultMap,在本文到此,使用的都是resultType。

其实在查询出来的每一个属性都是放在一个对应的Map里,使用字段名:字段值的方式,

在使用resultType能直接映射到pojo与数据库字段相同命名。

但有时候在不相同命名而存在相同类型时,或者在连接查询中,也需要使用到就需要用resultMap来指定。

1)简单单表查询

举一个简单的例子,用resultMap来代替

<select id="getUserList" resultType="user">
        select * from t_user
    </select>

代替后如下

<resultMap id="userResult" type="user">
        <result column="id" property="id"/>
        <result column="name" property="name"/>
        <result column="age" property="age"/>
        <result column="money" property="money"/>
    </resultMap>
    <select id="getUserList" resultMap="userResult">
        select * from t_user
    </select>

column为表的一个字段,property为映射到pojo中的属性名。这里比较特殊,字段名和属性名都相同。

2)多表连接查询

当然,在简单查询中无法看到resultMap的强大。接下来看看复杂的连接查询。

主要看表与表之间的数据映射有一对一、一对多和多的关系。这里也主要学习一下association和collection的使用。准备了4张表card、course、role、user_role。t_user与card是一对一的关系,t_user与course是一对多的关系,t_user与role是多对多关系。为了保存多对多关系,增加了user_role表。

以下为建表的sql语句,并为其增加数据(表中并未涉及到外键的应用)。

①建立多表语句
CREATE TABLE `card` (
  `id` int(11) NOT NULL,
  `cardNo` varchar(20) CHARACTER SET utf8 NOT NULL,
  `city` varchar(45) CHARACTER SET utf8 NOT NULL,
  `address` varchar(100) CHARACTER SET utf8 NOT NULL,
  `userid` int(11) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;

CREATE TABLE `course`(
`id`  int(11) NOT NULL AUTO_INCREMENT ,
`name`  varchar(45) NOT NULL ,
`userid`  int(11) NOT NULL ,
PRIMARY KEY (`id`)
)ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;

CREATE TABLE `role`(
`id`  int(11) NOT NULL AUTO_INCREMENT ,
`name`  varchar(45) NOT NULL ,
`desp`  varchar(45) NOT NULL ,
PRIMARY KEY (`id`)
)ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;

CREATE TABLE `user_role`(
`userid`  int(11) NOT NULL ,
`roleid` int(11)  NOT NULL ,
PRIMARY KEY (`userid`,`roleid`)
)ENGINE=InnoDB DEFAULT CHARSET=utf8;

INSERT INTO `card` VALUES ('1', 'cardNo', 'city', 'address', '1');

INSERT INTO `course` VALUES ('1', 'name', '1');
INSERT INTO `course` VALUES ('2', 'name2', '1');

INSERT INTO `role` VALUES ('1', 'name', 'desp');
INSERT INTO `role` VALUES ('2', 'name2', 'desp2');

INSERT INTO `user_role` VALUES ('1', '1');
INSERT INTO `user_role` VALUES ('1', '2');
②多表映射多个pojo类
public class Card {
    @Override
    public String toString(){
        return "Card[id=" + getId() +
                ",cardNo=" + getCardNo() +
                ",city=" + getCity() +
                ",address=" + getAddress() +
                ",userid=" + getUserid() +
                "]";
    }
    private int id;
    private String cardNo;
    private String city;
    private String address;
    private int userid;
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public String getCardNo() {
        return cardNo;
    }
    public void setCardNo(String cardNo) {
        this.cardNo = cardNo;
    }
    public String getCity() {
        return city;
    }
    public void setCity(String city) {
        this.city = city;
    }
    public String getAddress() {
        return address;
    }
    public void setAddress(String address) {
        this.address = address;
    }
    public int getUserid() {
        return userid;
    }
    public void setUserid(int userid) {
        this.userid = userid;
    }
}
public class Course {
    @Override
    public String toString(){
        return "Course[id=" + getId() +
                ",name=" + getName() +
                ",userid=" + getUserid() +
                "]";
    }
    private int id;
    private String name;
    private int userid;
    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 getUserid() {
        return userid;
    }
    public void setUserid(int userid) {
        this.userid = userid;
    }
}
public class Role {
    @Override
    public String toString(){
        return "Role[id=" + getId() +
                ",name=" + getName() +
                ",desp=" + getDesp() +
                "]";
    }
    private int id;
    private String name;
    private String desp;
    private List<User> users;
    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 String getDesp() {
        return desp;
    }
    public void setDesp(String desp) {
        this.desp = desp;
    }
    public List<User> getUsers() {
        return users;
    }
    public void setUsers(List<User> users) {
        this.users = users;
    }
}

在我们的原本的user类中,需要添加映射对应pojo类的变量,在User类中添加以下代码,将toString也要修改成输出一对一,一对多,多对多的关系。

    @Override
    public String toString(){
        String str = "Id:"+this.getId()+" Name:"+this.getName()
                +" Age:"+this.getAge()+" Money:"+this.getMoney()+"\n";
        if(card!=null)
            str += card.toString() + "\n";
        if(courses!=null)
            for (Course cours : courses)
                str += cours.toString()+"\n";
        if(roles!=null)
            for (Role role : roles)
                str += role.toString()+"\n";
        return str;
    }
    private Card card;
    private List<Role> roles;
    private List<Course> courses;
    public Card getCard(){
        return card;
    }
    public void setCard(Card card) {
        this.card = card;
    }
    public List<Role> getRoles() {
        return roles;
    }
    public void setRoles(List<Role> roles) {
        this.roles = roles;
    }
    public List<Course> getCourses() {
        return courses;
    }
    public void setCourses(List<Course> courses) {
        this.courses = courses;
    }
③实现语句

我们现在要实现以下这个复杂的连接查询语句。其实看上去复杂,只不过是将几张表进行左连接再通过id来查询罢了。

select 
a.id as user_id,a.name as user_name,a.age as user_age,a.money as user_money,
b.id as card_id,b.cardNo as crad_cradNo,b.userid as card_userid,b.city as card_city,b.address as card_address,
c.id as course_id,c.name as course_name,c.userid as course_userid,
e.name as role_name,e.desp as role_desp
from
t_user as a
left join card as b
on a.id=b.userid
left join course as c
on a.id=c.userid
left join user_role as d
on a.id=d.userid
left join role e
on d.roleid=e.id
where a.id=#{id}

在配置UserMapper.xml之前要先将上面新增的几个类起别名,方便下面书写,也是学以致用的表现嘛。通过直接用Package元素来将test.mybatis下的类使用类名来做别名。

    <typeAliases>
        <typeAlias type="test.DAO.User" alias="User"/>
        <package name="test.mybatis"/>
    </typeAliases>

将sql对应的xml在UserMapper.xml中增加,映射如下。

 <!--内嵌不复用-->
    <resultMap id="userResultTest" type="user">
        <result column="user_id" property="id"/>
        <result column="user_name" property="name"/>
        <result column="user_age" property="age"/>
        <result column="user_money" property="money"/>
        <association property="card" javaType="Card" columnPrefix="card_">
            <result column="id" property="id"/>
            <result column="no" property="cardNo"/>
            <result column="city" property="city"/>
            <result column="address" property="address"/>
            <result column="userid" property="userid"/>
        </association>
        <collection property="courses" javaType="ArrayList" ofType="Course" columnPrefix="course_">
            <result column="id" property="id"/>
            <result column="name" property="name"/>
            <result column="userid" property="userid"/>
        </collection>
        <collection property="roles" javaType="ArrayList" ofType="Role" columnPrefix="role_">
            <result column="id" property="id"/>
            <result column="name" property="name"/>
            <result column="desp" property="desp"/>
            <result column="userid" property="userid"/>
        </collection>
    </resultMap>

让我们来理解一下这段xml配置是什么意思。

在resultMap元素中配置属性id为唯一标识,在后面select元素中会通过resultMap属性来引用这个id,teyp属性在上面也说过,是映射类的别名。

result在resultMap在上面也说过,是数据库字段与类字段的映射。注意,这里的数据库字段在sql中用as起了别名。所以这里映射到的数据库字段也是使用sql语句中给这个字段起的别名。

association用来连接一对一关系的两个类,这里user与card为一对一关系。使用association来关联,其中属性property为card类在user中的属性字段名,用javaType来映射属性对应的pojo类。下面配置的result不再解释。

collection用来连接一对多和多对多关系的两个类,使用方式也大同小异。自己实验体会一下。

在我们配置完了需要使用的resultMap之后,我们就可以使用select元素来使用sql了,在resultMap的设置下方添加以下代码。就可以通过id:getuser来使用这条语句,获取的user中就可以获得card,course,role的关系了。

<select id="getuser" parameterType="int" resultMap="userResultTest1">
    select a.id as user_id,a.name as user_name,a.age as user_age,a.money as user_money
    ,b.id as card_id,b.cardNo as crad_cradNo,b.userid as card_userid,b.city as card_city,b.address as card_address
    ,c.id as course_id,c.name as course_name,c.userid as course_userid
    ,e.name as role_name,e.desp as role_desp
    from
    t_user as a
    left join card as b
    on a.id=b.userid
    left join course as c
    on a.id=c.userid
    left join user_role as d
    on a.id=d.userid
    left join role e
    on d.roleid=e.id
    where a.id=#{id}
</select>

下面我们来测试一下看Test代码。获得id为1的用户的所有关系。

@Test
public void testMap() throws Exception{
    String resource = "Mybatis/mybatis-config.xml";
    Reader reader = Resources.getResourceAsReader(resource);
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
    SqlSession session = sqlSessionFactory.openSession();
    String stat = "test.mybatis.DBMapping.UserMapper.getuser";
    User user = session.selectOne(stat,1);
    System.out.println(user.toString());
}

得到结果

Id:1 Name:张三 Age:24 Money:666.66
Card[id=1,cardNo=null,city=city,address=address,userid=1]
Role[id=0,name=name,desp=desp]
Role[id=0,name=name2,desp=desp2]
Course[id=1,name=name,userid=1]

通常像上面这种resultMap这种写法,在需要复用到card,courses,roles的resultMap就需要重写,这样会导致代码冗余,如果多次使用到这几个resultMap的话,我们可以写成下面这种外部引用的方式。

<!--外部引用复用-->
<resultMap id="cardResult" type="Card">
    <result column="id" property="id"/>
    <result column="no" property="cardNo"/>
    <result column="city" property="city"/>
    <result column="address" property="address"/>
    <result column="userid" property="userid"/>
</resultMap>
<resultMap id="courseResult" type="Course">
    <result column="id" property="id"/>
    <result column="name" property="name"/>
    <result column="userid" property="userid"/>
</resultMap>
<resultMap id="roleResult" type="Role">
    <result column="id" property="id"/>
    <result column="name" property="name"/>
    <result column="desp" property="desp"/>
    <result column="userid" property="userid"/>
</resultMap>
<resultMap id="userResultTest1" type="User">
    <result column="user_id" property="id"/>
    <result column="user_name" property="name"/>
    <result column="user_age" property="age"/>
    <result column="user_money" property="money"/>
    <association property="card" javaType="Card" resultMap="cardResult" columnPrefix="card_"/>
    <collection property="courses" javaType="ArrayList" ofType="Course" resultMap="courseResult" columnPrefix="course_"/>
    <collection property="roles" javaType="ArrayList" ofType="Role" resultMap="roleResult" columnPrefix="role_"/>
</resultMap>

可以看到,我们先定义了Card,Course,Role的resultMap,再在使用User的时候引入了这三者的resultMap的id值。这样可以复用三者的id。