MyBatis 高级映射

Mapper自动映射

MyBatis中,自动映射的三种模式:

  • NONE 表示不启用自动映射
  • PARTIAL 表示只对非嵌套的resultMap进行自动映射
  • FULL 表示对所有的resultMap都进行自动映射

默认的自动映射模式为PARTIAL。

在MyBatis全局配置文件中,在setting标签内设置自动映射模式:

<!--全局配置参数-->
<settings>
    <setting name="autoMappingBehavior" value="PARTIAL" />
</settings>

如果某些resultMap中不想使用自动映射,则可以单独在该resultMap中设置autoMapping属性为false:

<select id="findUserById" parameterType="Long" resultMap="UserResult" autoMapping="false">
    select id,name,email from t_user where id=#{id}
</select>

这里autoMapping属性会忽略全局配置文件中"outoMappingBehavior"映射模式。

如果Java包装类使用驼峰命名规则,则要开启mapUnderscoreToCamelCase属性,让自动映射机制将SQL查询出的非驼峰命名方式的字段名与Java包装类中的属性进行自动映射:

<settings>
    <setting name="mapUnderscoreToCamelCase" value="true" />
</settings>

Mapper配置动态SQL语句

根据一些查询条件来选择不同的SQL语句:

<select id="findUserList" parameterType="cn.com.mybatis.po.UserQueryVo" resultType="cn.com.mybatis.po.User">
    select * from user
    <where>
        <if test="UserQuertVo != null">
            <if test="UserQueryVo.gender != null and UserQueryVo.gender !=''">
                and user.sex=#{UserQueryVo.gender}
            </if>
            <if test="UserQueryVo.username != null and UserQueryVo.username != ''">
                and user.username like '%${UserQueryVo.username}%'
            </if>
        </if>
     </where>
</select>

当使用"<where>"标签对包裹if条件语句时,将会忽略查询条件中的第一个"and"。

将复用性比较强的SQL语句封装成SQL片段,但在SQL片段中不支持动态SQL语句的 <where> 标签:

<sql id="queryUserWhere">
    <!--要复用的SQL语句-->
</sql>

引用SQL映射配置:

<select id="findUserList" parameterType="cn.com.mybatis.po.UserQueryVo" resultType="cn.com.mybatis.po.User">
    select * from user
    <where>
        <include refid="queryUserWhere"></include>
        <!--在这里可能还要引用其他的SQL片段-->
     </where>
</select>

除了自身所在的Mapper文件,每个SQL映射配置还可以引用外部Mapper文件中的SQL片段,只需要在refid属性中填写SQL片段的id名添加所在Mapper文件的namespace信息即可。

有时查询语句可能包含多个查询信息,比如查询多个id的User用户:

select * from user where id in (2.4.6)

此时如果要在Mapper文件中配置这样的语句,这里定义一个Java包装类,其属性为一个包含多个id信息的List集合:

public class UserQueryVo{
    private List<Integer> ids;//包含多个id信息
    public List<Integer> getIds(){
        return ids;
    }
    public void setIds(List<Integer> ids){
        this.ids = ids;
    }
}

在Mapper中配置一个SQL片段,并在查询SQL映射中引入它:

<sql id="queryUserWhere">
    <if test="ids!=null">
        <foreach collection="ids" item="user_id" open="and id in (" close=")" separator=",">
        #{user_id}
        </foreach>
    </if>
</sql> 

<select id="findUserList" parameterType="cn.com.mybatis.po.UserQueryVo" resultType="cn.com.mybatis.po.User">
    select * from user
    <where>
        <include refid="query_user_where"></include>
    </where>
</select>

在SQL片段里的"and"用来拼接已有一个或多个查询条件的语句,当此语句为第一个查询条件时,会因为"<where>"的存在而屏蔽第一个"and"。

一对一查询

在实现一对一查询时,推荐使用resultType。

使用resultMap时,对映射输出的数据需要单独定义resultMap,过程有些繁琐,如果对查询结果有特殊的要求(比如JavaBean里面又包含有其他JavaBean)就可以使用。

使用resultType实现

比如查询一个购买批次的信息以及创建该批次的用户,一个批次对应着一个用户。批次表名为batch。

首先创建批次表batch对应的Java实体类Batch:

package cn.com.mybatis.po;  
  
import java.util.Date;  
import java.util.List;  
public class Batch {  
    private int batch_id;  
    private int cus_id;  
    private String number;  
    private Date createtime;  
    private String note;    
    public int getBatch_id() {  
        return batch_id;  
    }  
    public void setBatch_id(int batch_id) {  
        this.batch_id = batch_id;  
    }  
    //其他的get和set方法省略......
}

创建一个以batch为父类,然后追加用户信息:

package cn.com.mybatis.po;  
  
public class BatchCustomer extends Batch {  
    private String username;  
    private String acno;  
    public String getUsername() {  
        return username;  
    }  
    public void setUsername(String username) {  
        this.username = username;  
    }  
    public String getAcno() {  
        return acno;  
    }  
    public void setAcno(String acno) {  
        this.acno = acno;  
    }  
  
}

然后在UserMapper.xml映射文件中定义select类型的查询语句:

<select id="findBatchCustomer" resultType="cn.com.mybatis.po.BatchCustomer">  
    SELECT  
    batch.*,customer.username,customer.acno  
    FROM batch INNER JOIN customer  
    ON batch.cus_id = customer.cus_id 
</select>

编写测试方法:

@Test  
public void testBatchCustomer() throws Exception{  
    SqlSession sqlSession = dataConnection.getSqlSession();  
    List<BatchCustomer> batchCustomerList = sqlSession.selectList("test.findBatchCustomer");  
    if(batchCustomerList != null){  
        BatchCustomer batchCustomer = null;  
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");  
        for (int i=0;i<batchCustomerList.size();i++){  
            batchCustomer = batchCustomerList.get(i);  
            System.out.println("卡号为" + batchCustomer.getAcno() + "的名为" +batchCustomer.getUsername()+"的客户:\\n于"+sdf.format(batchCustomer.getCreatetime()) +"采购了批次号为"+batchCustomer.getNumber()+"的一批理财产品");  
        }  
    }  
    sqlSession.close();  
}

使用resultMap实现

使用resultMap可以映射实体类中包裹的其他实体类。

创建一个封装了批次属性和Customer实体类的BatchItem批次类:

package cn.com.mybatis.po;  
  
import java.util.Date;  
import java.util.List;  
public class BatchItem {  
    private int batch_id;  
    private int cus_id;  
    private String number;  
    private Date createtime;  
    private String note;  
    private Customer customer;   
    public int getBatch_id() {  
        return batch_id;  
    }  
    public void setBatch_id(int batch_id) {  
        this.batch_id = batch_id;  
    }  
}

SQL映射文件:

<!--一对一查询,使用resultMap实现-->  
<resultMap id="BatchInfoMap" type="cn.com.mybatis.po.BatchItem">  
    <id column="batch_id" property="batch_id"/>  
    <result column="cus_id" property="cus_id"/>  
    <result column="number" property="number"/>  
    <result column="createtime" property="createtime" javaType="Date"/>  
    <result column="note" property="note"/>  
    <association property="customer" javaType="cn.com.mybatis.po.Customer">  
        <id column="cus_id" property="cus_id"/>  
        <result column="username" property="username"/>  
        <result column="acno" property="acno"/>  
        <result column="gender" property="gender"/>  
        <result column="phone" property="phone"/>  
    </association>  
</resultMap>  
  
<select id="findBatchCustomerToMap" resultMap="BatchInfoMap">  
    SELECT  
    batch.*,customer.username,customer.acno  
    FROM batch INNER JOIN customer  
    ON batch.cus_id = customer.cus_id 
</select>

编写测试类:

@Test  
public void testBatchCustomerToMap() throws Exception{  
    SqlSession sqlSession = dataConnection.getSqlSession();  
    List<BatchItem> batchItemList = sqlSession.selectList("test.findBatchCustomerToMap");  
    if(batchItemList != null){  
        BatchItem batchItem = null;  
        Customer customer = null;  
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");  
        for (int i=0;i<batchItemList.size();i++){  
            batchItem = batchItemList.get(i);  
            customer = batchItem.getCustomer();  
            System.out.println("卡号为" + customer.getAcno() + "的名为" +customer.getUsername()+"的客户:\\n于"+sdf.format(batchItem.getCreatetime()) +"采购了批次号为"+batchItem.getNumber()+"的一批理财产品");  
        }  
    }  
}

一对多查询

比如,查询一批中一个用户信息,和有哪些理财产品。

修改一下Batch类:

package cn.com.mybatis.po;  
  
import java.util.Date;  
import java.util.List;  
public class Batch {  
    private int batch_id;  
    private int cus_id;  
    private String number;  
    private Date createtime;  
    private String note;  
    //批次中包含的理财产品订购信息
    private List<BatchDetail> batchDetials;  
    //get和set方法省略......
}

理财产品的订购信息实体类如下:

package cn.com.mybatis.po;  
  
import java.util.List;  
  
public class BatchDetail {  
    private int id;  
    private int batch_id;  
    private int product_id;  
    private int product_num;  
    private FinacialProduct finacialProduct;  
    //get和set方法省略...... 
}

编写SQL配置,使用extends标签继承上面一对一查询中的名为BatchInfoMap的resultMap:

<!--一对多-->  
<resultMap  
  id="BatchAndBatchDetailResultMap" type="cn.com.mybatis.po.BatchItem" extends="BatchInfoMap">  
    <collection  
  property="batchDetails" ofType="cn.com.mybatis.po.BatchDetail">  
        <!-- id:订单明细的唯一标识 -->  
  <id column="id" property="id"/>  
        <result column="batch_id" property="batch_id"/>  
        <result column="product_id" property="product_id"/> 
        <result column="product_num" property="product_num"/>  
    </collection>  
</resultMap>  
  
<select id="findBatchAndBatchDetail" resultMap="BatchAndBatchDetailResultMap">  
    SELECT  
        batch.*,  
        customer.username,customer.acno,  
        batchdetail.product_id,  
        batchdetail.product_num  
    FROM ((batch  
    INNER JOIN customer  
    ON batch.cus_id = customer.cus_id)  
    INNER JOIN batchdetail  
    ON batch.batch_id = batchdetail.batch_id)  
</select>

测试查询结果:

@Test  
public void testfindBatchAndBatchDetail() throws Exception{  
  
    SqlSession sqlSession=dataConnection.getSqlSession();  
    //调用userMapper的方法  
  BatchItem batchItem=  
            sqlSession.selectOne(  
                    "test" +  
                            ".findBatchAndBatchDetail");  
    if(batchItem!=null){  
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");  
        Customer customer = batchItem.getCustomer();//取出该批次的用户信息  
  //取出该批次订购的理财产品信息  
  List<BatchDetail> batchDetails = batchItem.getBatchDetails();  
        System.out.println("卡号为"+customer.getAcno()+"的名为"  
  +customer.getUsername()+"的客户:\\n于"  
  +sdf.format(batchItem.getCreatetime())+"采购了批次号为"  
  +batchItem.getNumber()+"的一批理财产品,详情如下:");  
        BatchDetail batchDetail = null;  
        if(batchDetails!=null){  
            for (int i = 0; i < batchDetails.size(); i++) {  
                batchDetail = batchDetails.get(i);  
                System.out.println("id为"+batchDetail.getProduct_id()  
                        +"的理财产品"+batchDetail.getProduct_num()+"份");  
            }  
        }  
    }  
    sqlSession.close();  
}

这里使用了association与collocation,一个映射单一实体对象,一个映射集合对象。

多对多查询

查询所有用户以及用户对应的批次订单中所有理财产品的详细信息。

修改后的Customer用户类,它包含用户信息以及用户下的所有批次信息:

package cn.com.mybatis.po;  
  
import java.io.Serializable;  
import java.util.List;  
  
public class Customer implements Serializable{  
    private int cus_id;  
    private String username;  
    private String acno;  
    private String gender;  
    private String phone;  
    private List<Batch> batchList;  
    //get和set方法省略
}

修改后的Batch批次信息类,它包含单个批次信息以及批次明细列表:

package cn.com.mybatis.po;  
  
import java.util.Date;  
import java.util.List;  
public class Batch {  
    private int batch_id;  
    private int cus_id;  
    private String number;  
    private Date createtime;  
    private String note;  
    private List<BatchDetail> batchDetials; 
    //get和set方法省略
}

修改后的BatchDetail批次明细类,它包含单个批次明细和对应的理财产品引用:

package cn.com.mybatis.po;  
  
import java.util.List;  
  
public class BatchDetail {  
    private int id;  
    private int batch_id;  
    private int product_id;  
    private int product_num;  
    private FinacialProduct  finacialProduct;  
    //get和set方法省略
}

新增的FinacialProduct产品明细类,包含理财产品的各种属性:

package cn.com.mybatis.po;  
  
import java.util.Date;  
  
public class FinacialProduct {  
    private int id;  
    private String name;  
    private double price;  
    private String detail;  
    private String imgpath;  
    private Date invattime;  
    //get和set方法省略
  
}

编写Mapper映射文件:

<resultMap  
  id="UserAndProductsResultMap" type="cn.com.mybatis.po.Customer">  
      <!-- 客户信息 -->  
  <result column="username" property="username"/>  
      <result column="acno" property="acno"/>  
  
      <!--批次订单信息,一个客户对应多个订单-->  
  <collection property="batchList" ofType="cn.com.mybatis.po.Batch">  
          <id column="batch_id" property="batch_id"/>  
          <result column="cus_id" property="cus_id"/>  
          <result column="number" property="number"/>  
          <result column="createtime" property="createtime" javaType="java.util.Date"/>  
          <result column="note" property="note"/>  
  
          <collection property="batchDetials" ofType="cn.com.mybatis.po.BatchDetail">  
              <!-- id:订单明细的唯一标识-->  
  <id column="id" property="id"/>  
              <result column="batch_id" property="batch_id"/>  
              <result column="product_id" property="product_id"/>  
              <result column="product_num" property="product_num"/>  
  
              <association property="finacialProduct" javaType="cn.com.mybatis.po.FinacialProduct">  
                  <id column="product_id" property="id"/>  
                  <result column="name" property="name"/>  
                  <result column="price" property="price"/>  
                  <result column="detail" property="detail"/>  
              </association>  
          </collection>  
      </collection>  
  </resultMap>  
  <select id="findUserAndProducts" resultMap="UserAndProductsResultMap">  
      SELECT  
  BATCH.*,  
  CUSTOMER.username,  
  CUSTOMER.acno,  
  BATCHDETAIL.product_id,  
  BATCHDETAIL.product_num,  
  FINACIAL_PRODUCTS.name,  
  FINACIAL_PRODUCTS.detail,  
  FINACIAL_PRODUCTS.price  
FROM  
  BATCH,  
  CUSTOMER,  
  BATCHDETAIL,  
  FINACIAL_PRODUCTS  
WHERE BATCH.cus_id = CUSTOMER.cus_id  
AND BATCHDETAIL.batch_id=BATCH.batch_id  
AND FINACIAL_PRODUCTS.product_id=BATCHDETAIL.product_id;  
  </select>

延迟加载

在MyBatis中,通常会进行多表联合查询,但是有时候并不会立即用到所有的联合查询结果。这种“按需查询”的机制,就可以使用延迟加载来实现。

开启延迟加载功能:

//在全局配置文件中配置setting属性
<configuration>
    <settings>
        <!--打开延迟加载的开关-->
        <setting name="lazyLoadingEnable" value="true" />
        <!--将积极加载改为按需加载-->
        <setting name="aggressiveLazyLoading" value="false" />
    </settings>
</configuration>

例如查询所有批次订单的信息,而每个批次订单中会关联查询用户,延迟加载用户信息,首先定义只查询所有批次订单信息的SQL配置:

<select id="findBatchUserLazyLoading" resultMap="BatchUserLazyLoadingResultMap">
    select * from batch
</select>

<!--延迟加载的resultMap-->
<resultMap id="BatchUserLazyLoadingResultMap" type="cn.com.mybatis.po.BatchItem">
    <!--对订单信息进行映射配置-->
    <id column="batch_id" property="batch_id" />
    <result column="cus_id" property="cus_id" />
    <result column="number" property="number" />
    <result column="createtime" property="createtime" javaType="java.util.Date" />
    <result column="note" property="note" />
    <!--实现延迟加载用户信息-->
    <association property="customer" javaType="cn.com.mybatis.po.Customer" select="findCustomerById" column="cus_id">
    </association>
</resultMap>

其中select用来指定Mapper.xml配置文件中的某个select标签对的id。

column是指订单信息中关联用户信息查询的列。

最后配置延迟加载要执行的获取用户信息的SQL:

<select id="findCustomerById" parameterType="int" resultType="cn.com.mybatis.po.Customer">
    select * from customer where cus_id=#{id}
</select>

其中输入参数就是association中column中定义的字段信息。

编写测试方法:

@Test  
    public void testFindBatchCustomerLazyLoading() throws Exception{  
          
        SqlSession sqlSession=dataConn.getSqlSession();
        //调用userMapper的方法,获取所有订单信息(未加载关联的用户信息)  
          List<BatchItem> batchItemList=sqlSession.selectList("findBatchUserLazyLoading"); 
          BatchItem batchItem = null;
          Customer customer = null;
        for (int i = 0; i < batchItemList.size(); i++) {  
            batchItem = batchItemList.get(i);  
            System.out.println("订单编号:"+batchItem.getNumber());
            //执行getCustomer时才会去查询用户信息,这里实现了延迟加载  
            customer=batchItem.getCustomer();  
            System.out.println("订购用户姓名:"+customer.getUsername());  
        }  
        sqlSession.close();
    }

综上所述,使用延迟加载方法,先执行简单的查询SQL(最好查询单表,也可以关联查询),再按需要加载关联查询的其他信息。

Mapper动态代理

新增一个CustomerMapper.xml,例如对Customer的增、删、改、查的SQL配置:

<?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="cn.com.mybatis.mapper.CustomerMapper">    
    <!-- 查询用户 -->
    <select id="findCustomerById" parameterType="int" resultType="cn.com.mybatis.po.Customer">  
      SELECT * FROM CUSTOMER WHERE cus_id=#{cus_id}
    </select>  
    <!-- 新增用户 -->   
    <insert id="insertCustomer" parameterType="cn.com.mybatis.po.Customer">    
        INSERT INTO CUSTOMER(username,acno,gender,phone) 
            value(#{username},#{acno},#{gender},#{phone})  
    </insert>
    <!-- 删除用户 -->  
    <delete id="deleteCustomer" parameterType="java.lang.Integer">  
        DELETE FROM CUSTOMER WHERE cus_id=#{cus_id}
    </delete>  
    <!-- 修改用户 -->  
    <update id="updateCustomerAcNo" parameterType="cn.com.mybatis.po.Customer" >
        UPDATE CUSTOMER SET acno = #{acno} WHERE cus_id=#{cus_id}
    </update>
</mapper>

其中,namespace中的路径为即将创建的mapper代理接口的路径。

使用CustomerMapper.xml的Mapper代理,创建CustomerMapper接口:

package cn.com.mybatis.mapper;

import cn.com.mybatis.po.Customer;
public interface CustomerMapper {

    public Customer findCustomerById(int id) throws Exception;  
      
    public void insertCustomer(Customer customer) throws Exception;  
      
    public void deleteCustomer(int id) throws Exception;  
      
    public void updateCustomerAcNo(Customer customer) throws Exception;  
}

测试动态代理效果:

@Test  
    public void testFindCustomerOnMapper() throws Exception{  
        SqlSession sqlSession=dataConn.getSqlSession();    
          
        //获取Mapper代理  
        CustomerMapper customerMapper=sqlSession.getMapper(CustomerMapper.class);
        //执行Mapper代理对象的查询方法
        Customer customer=customerMapper.findCustomerById(1);
        System.out.println("用户姓名:"+customer.getUsername()+"|"
                +"卡号:"+customer.getAcno());
        sqlSession.close();
    }

其中SqlSession类的getMapper方法的原理就是,根据Mapper代理接口的类型及Mapper.xml文件的内容,创建一个Mapper接口的实现类,其中实现了具体的增删改查方法。

我来评几句
登录后评论

已发表评论数()

相关站点

热门文章