MyBatis 实践

标签: Java与存储


动态SQL

动态SQL提供了对SQL语句的灵活操作,通过表达式进行判断,对SQL进行拼接/组装.


if

对查询条件进行判断,如果输入参数不为空才进行查询条件的拼接.

  • mapper
<select id="selectUser" resultType="com.fq.domain.User" parameterType="com.fq.domain.User">
SELECT * FROM user
<where>
<if test="id != null">
AND id = #{id}
</if>
<if test="name != null">
AND name = #{name}
</if>
<if test="password != null">
AND password = #{password}
</if>
</where>
</select>

<where/>会自动处理第一个AND(MyBatis还提供了自定义<where/>行为的<trim/>元素, 详细可参考MyBatis文档).

  • UserDAO
List<User> selectUser(User user) throws Exception;
  • Client
@Test
public void selectUserClient() throws Exception {
UserDAO dao = session.getMapper(UserDAO.class);
User user = dao.selectUser(new User(null, null, "new_password"));
System.out.println(user);
}

由于id与name为null, 因此这两个条件不会拼接在SQL中,这一点可以调试时日志中看出.


choose/when/otherwise

有些时候,我们并不想用到所有的条件语句,而只想从中选择一二.针对这种情况,MyBatis提供了<choose/>元素,他有点像Java中的switch.

<select id="selectUser" resultType="com.fq.domain.User" parameterType="com.fq.domain.User">
SELECT * FROM user
<where>
<choose>
<when test="id != null">
AND id = #{id}
</when>
<when test="name != null">
AND name = #{name}
</when>
<otherwise>
AND password = #{password}
</otherwise>
</choose>
</where>
</select>

set

用于动态更新语句的解决方案为<set/>,set元素可以被用于动态包含需要更新的列, 而舍去其他的.

<update id="updateUserById" parameterType="com.fq.domain.User">
UPDATE user
<set>
<if test="name != null">
name = #{name} ,
</if>
<if test="password != null">
password = #{password} ,
</if>
</set>
WHERE id = #{id};
</update>

foreach

使用foreach可以实现向SQL中传递数组List:

传入List

查询多个id的用户信息, 可以由下面两种SQL实现:

SELECT * FROM user WHERE (id = ? OR id = ? OR id = ?);
SELECT * FROM user WHERE id IN (?, ?, ?, ?);

因此其foreach的定义也有如下两种方案:

<select id="selectUser" parameterType="java.util.List" resultType="com.fq.domain.User">
SELECT *
FROM user
<where>
<if test="list != null and list.size >= 1">
<foreach collection="list" item="id" open="(" separator="or" close=")">
id = #{id}
</foreach>
</if>
</where>
</select>
<select id="selectUser" parameterType="java.util.List" resultType="com.fq.domain.User">
SELECT *
FROM user
<where>
<if test="list != null and list.size >= 1">
<foreach collection="list" item="id" open="id IN (" separator="," close=")">
#{id}
</foreach>
</if>
</where>
</select>
元素 描述
collection SQL解析的参数名
index 循环下标
item 单个元素的名
open 循环开始输出
close 循环结束输出
separator 中间分隔输出

传递List作为parameterType时,SQL解析参数名固定为list.

  • UserDAO
List<User> selectUser(List<Integer> ids) throws Exception;

批量插入用户案例

  • mapper
<insert id="insertUserList" parameterType="java.util.List">
INSERT INTO user(name, password) VALUES
<if test="list != null and list.size != 0">
<foreach collection="list" item="user" separator=",">
(#{user.name}, #{user.password})
</foreach>
</if>
</insert>
  • UserDAO
void insertUserList(List<User> users) throws Exception;
  • Client
@Test
public void insertUserListClient() throws Exception {
UserDAO dao = session.getMapper(UserDAO.class);
dao.insertUserList(Lists.newArrayList(new User(null, "mojia5", "mojia5"), new User(null, "mojia6", "mojia6"), new User(null, "mojia7", "mojia7")));
}

传入数组

  • mapper
<select id="selectUser" parameterType="Object[]" resultType="com.fq.domain.User">
SELECT *
FROM user
<where>
<if test="array != null and array.length >= 1">
<foreach collection="array" item="id" open="id IN (" separator="," close=")">
#{id}
</foreach>
</if>
</where>
</select>

List类似,传递数组作为parameterType时,SQL解析参数名固定为array.

  • UserDAO
List<User> selectUser(Integer[] ids) throws Exception;

SQL片段

可以将一段公共的SQL语句抽取出来, 作为一个SQL片段, 供其他SQL调用:

<sql id="user_where">
<if test="id != null">
AND id = #{id}
</if>
<if test="name != null">
AND name = #{name}
</if>
<if test="password != null">
AND password = #{password}
</if>
</sql> <select id="selectUser" resultType="com.fq.domain.User" parameterType="com.fq.domain.User">
SELECT * FROM user
<where>
<include refid="user_where"/>
</where>
</select>

经验:最好基于单表定义SQL片段,而且在SQL片段中不要包含<where>/<set>之类的标签,这样可以保证SQL片段重用度更高.


关联查询

  • 数据模型分析思路

    • 每张表的数据内容:分模块对每张表记录的内容进行熟悉,相当于学习系统需求/功能.
    • 每张表重要的字段:非空字段/外键字段等.
    • 表与表之间的数据库级别关系: 外键关系.
    • 表与表之间的业务关系:建立在某个业务的基础上去分析.
  • 订单/商品数据模型

    • 表内容

      • user: 购买商品的用户信息
      • order: 用户创建的订单
      • orderdetail: 订单详细(购买商品信息)
      • item: 商品信息
    • 表与表之间的业务关系:
      1. user/order:

        • user -> order: 一对多
        • order -> user: 一对一
      2. order/orderdetail:
        • order -> orderdetail:一对多
        • orderdetail -> order:一对一
      3. orderdetail/item:
        • orderdetail -> item:一对一
        • item -> orderdetail:一对多

‘一对一’查询

需求: 查询订单信息,关联查询(创建订单的)用户信息.

由以上分析可知主查询为order表,而order -> user关系为一对一, 因此使用resultMap将查询结果的订单信息映射到Order中,将用户信息映射到Order中的User属性.

  • PO: 改造User
public class User implements Serializable {

    private Integer id;

    private String username;

    private Date birthday;

    private Integer sex;

    private String address;

    // ...
}
  • PO: 新增Order, 将User组合到Order中:
public class Order implements Serializable {

    private Integer id;

    private Integer userId;

    private String number;

    private Date createTime;

    private String note;

    private User user;

    // ...
}
  • mapper
<?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.fq.mybatis.OrderDAO"> <cache type="org.mybatis.caches.ehcache.EhcacheCache"/> <resultMap id="order_user_map" type="com.fq.domain.Order">
<id column="id" property="id"/>
<result column="user_id" property="userId"/>
<result column="number" property="number"/>
<result column="create_time" property="createTime"/>
<result column="note" property="note"/> <association property="user" javaType="com.fq.domain.User">
<id column="user_id" property="id"/>
<result column="username" property="username"/>
<result column="birthday" property="birthday"/>
<result column="sex" property="sex"/>
<result column="address" property="address"/>
</association>
</resultMap> <select id="selectOrderWithUser" resultMap="order_user_map">
SELECT
`order`.*,
username,
birthday,
sex,
address
FROM `order`, user
WHERE `order`.user_id = user.id AND `order`.id = #{0};
</select> </mapper>

association: 映射关联查询的单条记录(将关联查询信息映射到PO对象属性).

  • OrderDAO
public interface OrderDAO {
Order selectOrderWithUser(Integer id) throws Exception;
}
  • Client
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:spring/applicationContext.xml")
public class OrderDAOClient { @Autowired
private OrderDAO dao; @Test
public void client() throws Exception {
Order order = dao.selectOrderWithUser(3);
System.out.println(order);
}
}

‘一对多’查询

需求: 查询订单及订单明细的信息(一对多).

  • PO: 定义OrderDetail,并在Order中添加List<OrderDetail> orderDetails订单明细属性:
public class OrderDetail implements Serializable {

    private Integer id;

    private Integer orderId;

    private Integer itemId;

    private Integer itemNumber;

    // ...
}
  • mapper
<resultMap id="order_user_detail_map" type="com.fq.domain.Order" extends="order_user_map">
<collection property="orderDetails" ofType="com.fq.domain.OrderDetail">
<id column="order_detail_id" property="id"/>
<result column="item_id" property="itemId"/>
<result column="item_num" property="itemNumber"/>
<result column="order_id" property="orderId"/>
</collection>
</resultMap> <select id="selectOrderWithDetail" resultMap="order_user_detail_map">
SELECT
`order`.*,
username,
birthday,
sex,
address,
orderdetail.id order_detail_id,
item_id,
item_num,
order_id
FROM `order`, user, orderdetail
WHERE `order`.user_id = user.id AND `order`.id = orderdetail.order_id AND `order`.id = #{0};
</select>
元素 描述
property 指定关联查询的结果集存储到的属性
ofType 指定关联查询结果集中的对象类型
  • OrderDAO
Order selectOrderWithDetail(Integer id) throws Exception;

‘多对多’查询

需求: 查询用户及用户购买商品信息.

由于User表与Item表没有直接关联,因此只能通过Order表与OrderDetail表进行关联.

  • 思路:

    1) 将用户信息映射到User中.

    2) 在User中添加List<Order>订单列表属性,将用户创建的订单映射到orders.

    3) 在Order中添加List<OrderDetail>订单明细列表属性,将订单的明细映射到orderDetails.

    4) 在OrderDetail中添加Item属性,将订单明细所对应的商品映射到item.

  • PO: Item

public class Item {

    private Integer id;

    private String name;

    private Float price;

    private String detail;

    private String pic;

    private Date createTime;

    //...
}
  • mapper
<resultMap id="user_item_map" type="com.fq.domain.User">
<id column="id" property="id"/>
<result column="username" property="username"/>
<result column="birthday" property="birthday"/>
<result column="sex" property="sex"/>
<result column="address" property="address"/> <collection property="orders" ofType="com.fq.domain.Order">
<id column="order_id" property="id"/>
<result column="id" property="userId"/>
<result column="order_create_time" property="createTime"/>
<result column="order_note" property="note"/>
<result column="order_number" property="number"/> <collection property="orderDetails" ofType="com.fq.domain.OrderDetail">
<id column="order_detail_id" property="id"/>
<result column="order_id" property="orderId"/>
<result column="item_id" property="itemId"/>
<result column="order_item_num" property="itemNumber"/> <association property="item" javaType="com.fq.domain.Item">
<id column="item_id" property="id"/>
<result column="item_create_time" property="createTime"/>
<result column="item_detail" property="detail"/>
<result column="item_name" property="name"/>
<result column="item_price" property="price"/>
<result column="item_pic" property="pic"/>
</association>
</collection>
</collection>
</resultMap> <select id="selectUserItem" resultMap="user_item_map">
SELECT
user.*,
`order`.id order_id,
`order`.create_time order_create_time,
`order`.note order_note,
`order`.number order_number,
orderdetail.id order_detail_id,
orderdetail.item_num order_item_num,
item.id item_id,
item.create_time item_create_time,
item.detail item_detail,
item.name item_name,
item.price item_price,
item.pic item_pic
FROM user, item, `order`, orderdetail
WHERE `order`.user_id = user.id AND orderdetail.order_id = `order`.id AND orderdetail.item_id = item.id ;
</select>
  • OrderDAO
List<User> selectUserItem() throws Exception;

resultMap小结:

使用<association/><collection/>可以完成一对一和一对多的高级映射.

  • association: 将关联查询信息映射到一个PO对象中.
  • collection: 将关联查询信息映射到一个集合中.

延迟加载

关联查询时,使用MyBatis 延迟加载 特性可有效减轻数据库压力.首次查询只查询主表信息,等需要时再去查询关联表信息.<resultMap/><association/>/<collection/>具备延迟加载功能.

需求: 查询订单信息并关联查询用户信息.

延迟加载开关

  • 在MyBatis核心配置文件(mybatis-configuration.xml)中配置:

    1) lazyLoadingEnabled : 设置是否懒加载.默认false,则所有关联查询都会被初始化加载.

    2) aggressiveLazyLoading : 设置是否积极加载. 默认true,所有关联属性被初始化加载.

  • Settings配置:

<settings>
<setting name="cacheEnabled" value="true"/>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="false"/>
</settings>

Mapper

  • 只查询订单信息
<resultMap id="order_user_map" type="com.fq.domain.Order">
<id column="id" property="id"/>
<result column="user_id" property="userId"/>
<result column="number" property="number"/>
<result column="create_time" property="createTime"/>
<result column="note" property="note"/> <association property="user" javaType="com.fq.domain.User" select="com.fq.mybatis.UserDAO.selectUserById" column="user_id"/>
</resultMap> <select id="selectOrderWithUser" resultMap="order_user_map">
SELECT *
FROM `order`;
</select>
元素 描述
select 指定关联查询Statementcom.fq.mybatis.UserDAO.selectUserById.
column 指定关联查询时将users_id值传入selectUserById.
  • 关联查询用户信息(namespacecom.fq.mybatis.UserDAO)
<select id="selectUserById" parameterType="java.lang.Integer" resultType="com.fq.domain.User">
SELECT *
FROM user
WHERE id = #{id};
</select>

将上面查询到的订单信息中的user_id传入selectUserById来关联查询用户信息.

  • OrderDAO(同前)
List<Order> selectOrderWithUser() throws Exception;
  • Client
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:spring/applicationContext.xml")
public class OrderDAOClient { @Autowired
private OrderDAO dao; @Test
public void client() throws Exception {
List<Order> orders = dao.selectOrderWithUser();
for (Order order : orders) {
System.out.println(order.getUser());
}
}
}

debug上面Client, 观察log信息,会发现只有当确实需要User信息时才会调用selectUserById.


MyBatis 实践 -动态SQL/关联查询的更多相关文章

  1. MyBatis的动态SQL操作--查询

    查询条件不确定,需要根据情况产生SQL语法,这种情况叫动态SQL,即根据不同的情况生成不同的sql语句. 模拟一个场景,在做多条件搜索的时候,

  2. 8.mybatis动态SQL模糊查询 (多参数查询,使用parameterType)

    多参数查询,使用parameterType.实例: 用户User[id, name, age] 1.mysql建表并插入数据 2.Java实体类 public class User { public ...

  3. 利用MyBatis的动态SQL特性抽象统一SQL查询接口

    1. SQL查询的统一抽象 MyBatis制动动态SQL的构造,利用动态SQL和自定义的参数Bean抽象,可以将绝大部分SQL查询抽象为一个统一接口,查询参数使用一个自定义bean继承Map,使用映射 ...

  4. Mybatis中动态SQL多条件查询

    Mybatis中动态SQL多条件查询 mybatis中用于实现动态SQL的元素有: if:用if实现条件的选择,用于定义where的字句的条件. choose(when otherwise)相当于Ja ...

  5. MyBatis中动态SQL语句完成多条件查询

    一看这标题,我都感觉到是mybatis在动态SQL语句中的多条件查询是多么的强大,不仅让我们用SQL语句完成了对数据库的操作:还通过一些条件选择语句让我们SQL的多条件.动态查询更加容易.简洁.直观. ...

  6. MyBatis:学习笔记(3)——关联查询

    MyBatis:学习笔记(3)--关联查询 关联查询 理解联结 SQL最强大的功能之一在于我们可以在数据查询的执行中可以使用联结,来将多个表中的数据作为整体进行筛选. 模拟一个简单的在线商品购物系统, ...

  7. mybatis.5.动态SQL

    1.动态SQL,解决关联sql字符串的问题,mybatis的动态sql基于OGNL表达式 if语句,在DeptMapper.xml增加如下语句; <select id="selectB ...

  8. mybatis 使用动态SQL

    RoleMapper.java public interface RoleMapper { public void add(Role role); public void update(Role ro ...

  9. MyBatis框架——动态SQL、缓存机制、逆向工程

    MyBatis框架--动态SQL.缓存机制.逆向工程 一.Dynamic SQL 为什么需要动态SQL?有时候需要根据实际传入的参数来动态的拼接SQL语句.最常用的就是:where和if标签 1.参考 ...

随机推荐

  1. iOS-音频格式转换-b

    iOS处理音频过程中有时候需要不同格式的音频进行转换,最近需要将m4a格式的音频转换成wav,在网上搜索之后代码整理如下: - (void)convetM4aToWav:(NSURL *)origin ...

  2. c++ 哪些自定义的数据类型

    http://www.cnblogs.com/ShaneZhang/archive/2013/06/21/3147648.html 这些数据类型是 C99 中定义的,具体定义在:/usr/includ ...

  3. css3技巧——产品列表之鼠标滑过效果(一)

    查看效果: http://www.daqianduan.com/example?pid=6117 html代码: <div class="main"> <div ...

  4. What is XMLHTTP? How to use security zones in Internet Explorer

    Types of Security Zones Internet Zone This zone contains Web sites that are not on your computer or ...

  5. SPOJ 3643 /BNUOJ 21860 Traffic Network

    题意:现在已有m条单向路,问在给你的k条双向路中选择一条,使得s到t的距离最短 思路:设双向路两端点为a,b;长度为c. s到t的有三种情况: 1:原本s到t的路径 2:从s到a,a到b,b再到t的路 ...

  6. POJ 3070 Fibonacci(矩阵快速幂)

    题目链接 题意 : 用矩阵相乘求斐波那契数的后四位. 思路 :基本上纯矩阵快速幂. #include <iostream> #include <cstring> #includ ...

  7. 黑马程序员--C#中属性和字段(变量)的区别

    ---------------------- ASP.Net+Android+IOS开发..Net培训.期待与您交流! ---------------------- 属性为类提供了一种很有用的封装数据 ...

  8. TopCoder SRM 633div1

    250pts   PeriodicJumping 题意:从起点开始,每次按找数组jump给定的长度,即jump[0], jump[1], jump[2].....jump[n-1], 向各个方向跳,跳 ...

  9. IE11兼容IE9问题

    注意如果<head>标签里加<meta http-equiv="X-UA-Compatible"content="IE=EmulateIE9" ...

  10. lintcode:最大子数组差

    题目 最大子数组差 给定一个整数数组,找出两个不重叠的子数组A和B,使两个子数组和的差的绝对值|SUM(A) - SUM(B)|最大. 返回这个最大的差值. 样例 给出数组[1, 2, -3, 1], ...