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接口的实现类,其中实现了具体的增删改查方法。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。