MyBatis的關聯對映和動態SQL
一、MyBatis的關聯對映
在實際開發中,實體與實體之間不是孤立存在的,往往實體與實體之間是存在關聯的;例如班級中可以多個學生,每個學生屬於一個班級,這種例項之間相互訪問就是關聯關係。關聯關係分為三類:一對一,一對多,多對多。
1.一對一
比如說,一個人只能有一個身份證,一個身份證只能給一個人使用,這就是一對一的關係,下面例項MyBatis怎麼處理一對一的關係對映。
首先在資料庫中分別建立兩個表為card和person
create table card( id int(11) primary key auto_increment, code varchar(18) ); create table person( id int(11) primary key auto_increment, name varchar(18), sex varchar(3), age int(4), card_id int(11) unique, foreign key (card_id) references card (id) );
在專案中建立兩個實體與表對應
public class Person implements Serializable { /** 主鍵 */ private Integer id; /** 姓名 */ private String name; /** 性別 */ private String sex; /** 年齡 */ private Integer age; /** 對應card實體 */ private Card card; //省略get/set方法 } public class Card implements Serializable { /** 主鍵 */ private Integer id; /** 身份證號 */ private String code; //省略get/set方法 }
編寫對應的MyBatis的CardMapper.xml檔案和PersonMapper.xml檔案
<mapper namespace="com.hu.mapper.CardMapper"> <!--根據id查詢card--> <select id="selectCardById" parameterType="Integer" resultType="com.hu.model.Card"> select * from card where id = #{id} </select> </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.hu.mapper.PersonMapper">
<select id="selectPersonById" parameterType="Integer" resultMap="">
select * from person where id = #{id}
</select>
<!--對映Person物件-->
<resultMap id="personMap" type="com.hu.model.Person">
<id property="id" column="id"/>
<result property="name" column="name" />
<result property="sex" column="sex" />
<result property="age" column="age" />
<!--一對一關聯對映使用:association-->
<association property="card" column="card_id" select="com.hu.mapper.selectCardById" javaType="com.hu.model.Card"/>
</resultMap>
</mapper>
其中:<association>就是在一對一關聯關係中使用,其select屬性表示要執行的查詢的mappper命名+id,column屬性的card_id就是執行sql語句傳入的id。
編寫與xml相對應的介面:
public interface PersonMapper {
/**
* 根據id查詢person
* 方法名和引數必須和相對應的xml檔案中元素id屬性和parameterType屬性一致
* @param id
* @return
*/
Person selectPersonById(Integer id);
}
對應的測試類:
public static void main(String[] args) throws Exception{
//讀取mybatis-config.xml檔案
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
//初始化mybatis
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//建立Session實體
SqlSession session = sqlSessionFactory.openSession();
//獲得mapper介面的代理物件
PersonMapper personMapper = session.getMapper(PersonMapper.class);
Person person = personMapper.selectPersonById(1);
System.out.println(person);
System.out.println(person.getCard());
CardMapper cardMapper = session.getMapper(CardMapper.class);
Card card = cardMapper.selectCardById(1);
//提交事務
session.commit();
//關閉事務
session.close();
}
2、一對多
舉例說明:一個班級可以有多個學生,一個學生只能屬於一個班級,班級和學生就是一對多的關係,學生和班級就是多對一的關係。在資料庫建表時,一對多的關係通常通過主外來鍵來關聯,並且外來鍵應在多方,即多方維護關係。
首先在資料庫中建立兩個表clazz和student。
create table clazz(
id int(11) primary key auto_increment,
code varchar(10)
);
create table student(
id int(11) primary key auto_increment,
name varchar(10),
sex varchar(10),
age int(11),
clazz_id int(11),
foreign key (clazz_id) references clazz(id)
);
然後建立對應的表的實體。
public class Clazz implements Serializable{
/** 對應主鍵 */
private Integer id;
/** 班級編號 */
private String code;
/** 班級包含的學生數量 */
private List<Student> students;
//省略get/set方法
}
public class Student implement Serializable {
/** 對應著主鍵 */
private Integer id;
/** 學生名字 */
private String name;
/** 性別 */
private String sex;
/** 年齡 */
private String age;
/** 所屬班級 */
private Clazz clazz;
//省略get/set方法
}
編寫對應班級的xml對映檔案。
<mapper namespace="com.hu.mapper.ClazzMapper">
<!--根據id查詢班級資訊,返回resultMap-->
<select id="selectClazzById" parameterType="Integer" resultMap="clazzResultMap">
select * from clazz where id = #{id}
</select>
<!--對映Clazz物件的resultMap-->
<resultMap id="clazzResultMap" type="com.hu.model.Clazz">
<id property="id" column="id" />
<result property="code" column="code" />
<result property="name" column="name" />
<!--一對多關聯對映使用:collection fetchType="lazy"表示懶載入-->
<collection property="students" javaType="ArrayList" column="id" ofType="com.hu.model.Student"
select="com.hu.mapper.StudentMapper.selectStudentByClazzId" fetchType="lazy">
<id property="id" column="id" >
<result property="name" column="name" />
<result property="sex" column="sex" />
<result property="age" column="age" />
</collection>
</resultMap>
</mapper>
其中:<collection>就是在一對多關聯關係中使用,其select屬性表示要執行的查詢的mappper命名+id,column屬性的id就是執行sql語句傳入的id。一個新的屬性fetchType,有兩種取值分別為eager和lazy,eager表示立即去載入,即在查詢Clazz物件的時候,會馬上去載入關聯的selectStudentByClazzId中定義的sql去查詢出班級的資訊;lazy表示懶載入,在查詢Clazz物件的時候不會馬上去執行關聯的selectStudentByClazzId這個語句,只有在Clazz需要用到students的時候才會去執行關聯的selectStudentByClazzId。
使用懶載入還需在mybatis-config.xml中增加配置
<settings>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="false" />
</settings>
其中:lazyLoadingEnabled —— 屬性表示延遲載入的全域性開關。當開啟時,所有關聯物件都會延遲載入。預設為false
aggressiveLazyLoading —— 啟動時,會使帶有延遲載入屬性的物件立即載入;反之,每種屬性將會按需載入。預設為true,所以這裡設定為false。
然後編寫對應的StudentMapper.xml檔案:
<mapper namespace="com.hu.mapper.StudentMapper">
<!--根據id查詢學生資訊-->
<select id="selectStudentById" parameterType="Integer" resultMap="studentResultMap">
select * from clazz c,student stu where c.id = stu.clazz_id and stu.id = #{id}
</select>
<!--根據班級id查詢學生資訊,返回resultMap-->
<select id="selectStudentByClazzId" parameterType="Integer" resultMap="studentResultMap">
select * from student where clazz_id = #{id}
</select>
<!--對映Student物件的resultMap-->
<resultMap id="studentResultMap" type="com.hu.model.Student">
<id property="id" column="id" />
<result property="name" column="name" />
<result property="sex" column="sex" />
<result property="age" column="age" />
<!--多對一的關聯對映:association-->
<association property="clazz" javaType="com.hu.model.Clazz" >
<id property="id" column="id" />
<result property="code" column="code" />
</association>
</resultMap>
</mapper>
對應的ClazzMapper和StudentMapper的介面。
public interface StudentMapper {
/**
* 根據id查詢學生資訊
* @param id
* @return
*/
Student selectStudentById(Integer id);
}
public interface ClazzMapper {
/**
* 根據id查詢班級資訊
* @param id
* @return
*/
Clazz selectClazzById(Integer id);
}
最後就是測試方法:
public class Test2 {
public static void main(String[] args) throws Exception{
//讀取mybatis-config.xml檔案
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
//初始化mybatis
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//建立Session實體
SqlSession session = sqlSessionFactory.openSession();
//獲得mapper介面的代理物件
ClazzMapper clazzMapper = session.getMapper(ClazzMapper.class);
// CardMapper cardMapper = session.getMapper(CardMapper.class);
Clazz clazz = clazzMapper.selectClazzById(1);
//提交事務
session.commit();
//關閉事務
session.close();
}
}
3、多對多
舉例說明:一個訂單可以有多件商品,一種商品可以屬於多個訂單,訂單和商品就是多對多的關係。對於資料庫中多對多的關係一般使用一箇中間表來維護關係,中間表中分別存放訂單表的主鍵id和商品表的主鍵id。
首先在資料庫中建立一個使用者表user,一個訂單表orders,一個商品表goods以及一個維護訂單表和商品表的中間表order_goods,sql語句如下:
#使用者表
create table user(
id int(11) primary key auto_increment,
username varchar(10),
loginname varchar(10),
password varchar(10),
phone varchar(11),
address varchar(32)
);
#訂單表
create table orders(
id int(11) primary key auto_increment,
code varchar(16),
total double(10,2),
user_id int(11),
foreign key (user_id) references user(id)
);
#商品表
create table goods(
id int(11) primary key auto_increment,
name varchar(10),
price double(10,2),
remark varchar(32)
);
#中間表
create table order_goods(
order_id int(11),
goods_id int(11),
amount int(11),
primary key(order_id,goods_id),
foreign key (order_id) references orders(id),
foreign key(goods_id) references goods(id)
);
在程式中建立與表對應的實體:
public class Goods implements Serializable {
/** 主鍵 */
private Integer id;
/** 商品名稱 */
private String name;
/** 商品價格 */
private Double price;
/** 商品描述 */
private String remark;
/** 商品所屬訂單 */
private List<Orders> orders;
//省略get/set方法。。。
}
public class Orders implements Serializable {
/** 主鍵 */
private Integer id;
/** 訂單編號 */
private String code;
/** 訂單總金額 */
private Double total;
/** 訂單所屬使用者 */
private User2 user2;
/** 訂單包含的商品 */
private List<Goods> goods;
//省略get/set方法。。。
}
public class User2 implements Serializable {
/** 主鍵 */
private Integer id;
/** 使用者名稱 */
private String username;
/** 登入名 */
private String loginname;
/** 密碼 */
private String password;
/** 手機號 */
private String phone;
/** 地址 */
private String address;
/** 使用者的訂單 */
private List<Orders> orders;
//省略get/set方法。。。
}
然後對應的xml對映檔案UserMapper.xml
<mapper namespace="com.hu.mapper.UserMapper">
<resultMap id="userResultMap" type="com.hu.model.User">
<id property="id" column="id"></id>
<result property="username" column="username"></result>
<result property="loginname" column="loginname"></result>
<result property="password" column="password"></result>
<result property="phone" column="phone"></result>
<result property="address" column="address"></result>
<!--一對多對映使用:collection-->
<collection property="orders" javaType="ArrayList"
select="com.hu.mapper.OrdersMapper.selectOrdersByUserId" >
<id property="id" column="id"></id>
<result property="code" column="code"></result>
<result property="total" column="total"></result>
</collection>
</resultMap>
<!--根據id查詢使用者資訊-->
<select id="selectUserById" parameterType="Integer" resultMap="userResultMap">
select * from user where id = #{id}
</select>
</mapper>
然後是對應的OrderMapper.xml
<mapper>
<resultMap id="ordersResultMapper" type="com.hu.model.Orders">
<id property="id" column="oid"></id>
<result property="code" column="code"></result>
<result property="total" column="total"></result>
<association property="user" javaType="com.hu.model.User2">
<id property="id" column="id"></id>
<result property="username" column="username"></result>
<result property="loginname" column="loginname"></result>
<result property="password" column="password"></result>
<result property="phone" column="phone"></result>
<result property="address" column="address"></result>
</association>
<collection property="goods" javaType="ArrayList" column="oid" ofType="com.hu.model.GoodsMapper.selectGoodsByOrderId">
<id property="id" column="id"></id>
<result property="name" column="name"></result>
<result property="price" column="price"></result>
<result property="remark" column="remark"></result>
</collection>
</resultMap>
<!--注意,如果查詢出來的列同名,例如user表的id和order表的id都是id,同名,則需要別名進行區分-->
<!--根據使用者id查詢訂單資訊-->
<select id="selectOrdersById" parameterType="Integer" resultMap="ordersResultMapper">
select u.*,o.id as oid,o.code as code,o.total as total,o.user_id as user_id
from user u,orders o where u.id = o.user_id and o.id = #{id}
</select>
</mapper>
對應的GoodsMapper.xml檔案
<mapper namespace="com.hu.mapper.GoodsMapper">
<select id="selectGoodsByOrderId" parameterType="Integer" resultType="com.hu.model.Goods">
select * from goods where id in (select goods_id from order_goods where order_id = #{id})
</select>
</mapper>
UserMapper.xml檔案對應的介面
public interface UserMapper {
User2 selectUserById(Integer id);
}
OrdersMapper.xml檔案對應的介面
public interface OrdersMapper {
Orders selectOrdersById(Integer id);
}
最後就是測試類:
public class Test3 {
public static void main(String[] args) throws Exception {
//讀取mybatis-config.xml檔案
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
//初始化mybatis,建立SqlSessionFactory類的例項
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//建立Session例項
SqlSession session = sqlSessionFactory.openSession();
Test3 test3 = new Test3();
}
//測試一對多關係,查詢客戶User聯合查詢訂單Orders
public void testSelectUserById(SqlSession session) {
//獲得UserMapper介面的代理物件
UserMapper userMapper = session.getMapper(UserMapper.class);
//呼叫selectUserById方法
User2 user2 = userMapper.selectUserById(1);
//檢視查詢的user物件的資訊
System.out.println(user2.getId()+","+user2.getUsername());
//檢視user物件關聯的訂單資訊
List<Orders> ordersList = user2.getOrders();
for (Orders order : ordersList) {
System.out.println(order);
}
}
//測試多對多關係,查詢訂單Orders的時候關聯查詢訂單的商品Goods
public void testSelectOrderById(SqlSession session) {
//獲得OrdersMapper介面的代理物件
OrdersMapper ordersMapper = session.getMapper(OrdersMapper.class);
//呼叫selectOrderById方法
Orders orders = ordersMapper.selectOrdersById(2);
//檢視查詢到的order物件資訊
System.out.println(orders.getId()+","+orders.getCode()+","+orders.getTotal());
//檢視orders物件關聯的使用者資訊
User2 user2 = orders.getUser2();
System.out.println(user2);
//檢視orders物件關聯的商品資訊
List<Goods> goodsList = orders.getGoods();
for (Goods good : goodsList) {
System.out.println(good);
}
}
}
二、動態SQL
動態sql元素喝使用jstl或其他類似基於xml的文字處理器相似,MyBatis採用功能強大的基於ognl的表示式來完成動態sql的編寫。ognl的表示式可以被用在任意的sql對映語句中。
常用的動態sql元素包含:
·if
·choose(when、otherwise)
·where
·set
·foreach
·bind
先建立需要用到的例項的表employee和對應的實體
create table employee(
id int(11) primary key auto_increment,
loginname varchar(16),
password varchar(16),
name varchar(10),
sex char(2) default null,
age int(4),
phone varchar(16),
sal double(10,2),
state varchar(18)
);
public class Employee implements Serializable {
/** 主鍵id */
private Integer id;
/** 登入名 */
private String loginname;
/** 密碼 */
private String password;
/** 真是姓名 */
private String name;
/** 性別 */
private String sex;
/** 年齡 */
private Integer age;
/** 電話 */
private String phone;
/** 工資 */
private Double sal;
/** 狀態 */
private String state;
//省略get/set方法
}
2.1、if語句
動態sql通常會做的事情就是有條件的包含where子句的一部分,這時就可以使用if語句來實現,例:
<mapper>
<select id="selectEmployeeByIdLike" resultType="com.hu.model.Employee">
select * from employee where state = 'ACTIVE'
<!-- 可選條件,如果傳進來的引數有id屬性,則加上id查詢條件-->
<if test="id !=null">
and id =#{id}
</if>
</select>
</mapper>
上述語句中存在一個if條件。如果沒有傳入id,那麼所有state為‘ACTIVE’狀態的Employee都會被返回,如果沒有傳id,那麼就會查詢id內容的Employee結果返回。
定義與EmployeeMapper.xml與之對應的介面EmployeeMapper
public interface EmployeeMapper {
List<Employee> selectEmployeeByIdLike(HashMap<String,Objects> params);
}
上述介面傳入的是一個HashMap作為引數,在MyBatis中,#{id}表示式獲取引數有兩種方式:一是從HashMap中獲取集合中的property物件;二是從javabean中獲取property物件。
public class EmployeeIfTest {
public static void main(String[] args) throws Exception{
//讀取mybatis-config.xml檔案
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
//初始化mybatis,建立SqlSessionFactory類的例項
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//建立Session例項
SqlSession session = sqlSessionFactory.openSession();
EmployeeIfTest emTest = new EmployeeIfTest();
}
public void testSelectEmployeeByIdLike(SqlSession session) {
//獲得EmployeeMapper介面的代理物件
EmployeeMapper employeeMapper = session.getMapper(EmployeeMapper.class);
//建立一個HaspMap儲存引數
HashMap<String,Object> params = new HashMap<String,Object>();
//設定id屬性
params.put("id",1);
//呼叫EmployeeMapper介面的selectEmployeeByIdLike方法
List<Employee> list = employeeMapper.selectEmployeeByIdLike(params);
//檢視查詢結果
list.forEach(e-> System.out.println(e));
}
}
2.2、choose(when、otherwise)
有時候不想用所有的條件語句,而是隻想從中選擇其中的某些條件,這個時候就可以使用choose元素,它有點想java中的switch語句。
舉例:如果提供了id就按id查詢,提供了loginname和password就按loginname和password查詢,如果兩者都沒有就按性別等於男去查詢
<select id="selectEmployeeChoose" parameterType="hashmap" resultType="com.hu.model.Employee">
select * from employee where state = 'ACTIVE'
<!-- 如果傳入id,就根據id查詢,沒有傳入id就根據loginname和password查詢,否者查詢sex為男的資料-->
<choose>
<when test="id !=null">
and id = #{id}
</when>
<when test="loginname !=null and password !=null">
and loginname = #{loginname} and password = #{password}
</when>
<otherwise>
and sex='男'
</otherwise>
</choose>
</select>
在介面中新增對應的方法和測試方法
List<Employee> selectEmployeeChoose(HashMap<String,Object> params);
public void testSelectEmployeeChoose(SqlSession session) {
//獲得EmployeeMapper介面的代理物件
EmployeeMapper employeeMapper = session.getMapper(EmployeeMapper.class);
//建立一個HaspMap儲存引數
HashMap<String,Object> params = new HashMap<String,Object>();
//設定id屬性
params.put("id",1);
params.put("loginname","jack");
params.put("password","111111");
List<Employee> list = employeeMapper.selectEmployeeChoose(params);
list.forEach(e-> System.out.println(e));
}
2.3、where元素
where元素知道只有在一個以上的if條件有值的情況下才去插入where子句。而且,若最後的內容是“and”或“or”開頭,則where元素也知道如何將它們去除。
<select id="selectEmplyeeLike" resultType="com.hu.model.Employee">
select * from employee
<where>
<if test="state !=null">
state = #{state}
</if>
<if test="id !=null">
and id = #{id}
</if>
<if test="loginname = #{loginname} and password = #{password}">
and loginname = #{loginname} and password = #{password}
</if>
</where>
</select>
2.4、set元素
動態更新語句還可以使用set元素。set元素可以被用於動態包含需要更新的列,而捨棄其他的。
對應的xml檔案如下:
<!--根據id查詢員工資訊-->
<select id="selectEmployeeWithId" parameterType="Integer" resultType="com.hu.model.Employee">
select * from employee where id = #{id}
</select>
<!--動態更新員工資訊-->
<update id="updateEmployee" parameterType="com.hu.model.Employee">
update employee
<set>
<if test="loginame !=null">loginname=#{loginname},</if>
<if test="password !=null">password = #{password},</if>
<if test="name != null">name = #{name},</if>
<if test="sex != null">sex = #{sex},</if>
<if test="age != null">age = #{age},</if>
<if test="phone != null">phone = #{phone},</if>
<if test="sal != null">sal = #{sal},</if>
<if test="state != null">state = #{state}</if>
</set>
where id = #{id}
</update>
set元素會動態前置set關鍵字,同時會消除無關的逗號,因為使用了條件語句之後可能就會在生成的賦值語句的後面留下這些逗號。
xml檔案對應的介面
//根據id查詢員工
Employee selectEmployeeWithId(Integer id);
void updateEmployee(Employee employee);
最後就是測試方法:
public void testUpdateEmployee(SqlSession session) {
//獲得EmployeeMapper介面的代理物件
EmployeeMapper employeeMapper = session.getMapper(EmployeeMapper.class);
//查詢id為4的員工資訊
Employee employee = employeeMapper.selectEmployeeWithId(4);
//設定修改的屬性
employee.setLoginname("mary");
employee.setPassword("111111");
employee.setName("馬力");
employeeMapper.updateEmployee(employee);
}
2.5 、foreach元素
sql語句有時需要對一個集合進行遍歷,這個時候就需要使用foreach元素,它允許指定一個集合,宣告可以用在元素體內的集合項和索引變數。它也允許指定開閉匹配的字串以及在迭代中間放置分隔符。foreach元素是很智慧的,因此它不會隨機地附加多餘的分隔符。
舉例,對應的xml檔案:
<select id="selectEmployeeIn" resultType="com.hu.model.Employee">
select * from employee where id in
<foreach collection="list" item="item" index="index" open="(" separator="," close=")">
#{item}
</foreach>
</select>
對應xml檔案的介面為:
//根據傳入的id集合查詢員工
List<Employee> selectEmployeeIn(List<Integer> ids);
測試方法為:
public void testSelectEmployeeIn(SqlSession session) {
//獲得EmployeeMapper介面的代理物件
EmployeeMapper employeeMapper = session.getMapper(EmployeeMapper.class);
//建立List集合
List<Integer> ids = new ArrayList<>();
ids.add(1);
ids.add(2);
List<Employee> list = employeeMapper.selectEmployeeIn(ids);
list.forEach(e-> System.out.println(e));
}
2.6、bind元素
bind元素可以從OGNL表示式中建立一個變數並將其繫結到上下文。
舉例,對應的xml檔案:
<select id="selectEmployeeLikeName" resultType="com.hu.model.Employee">
<bind name="pattern" value="'%' + parameter.getName()+'%'" />
select * from employee
where loginname like #{pattern}
</select>
對應的介面:
//進行模糊查詢
List<Employee> selectEmployeeLikeName(Employee employee);
最後測試方法為:
public void testSelectEmployeeLikeName(SqlSession session) {
//獲得EmployeeMapper介面的代理物件
EmployeeMapper employeeMapper = session.getMapper(EmployeeMapper.class);
Employee employee = new Employee();
employee.setName("o");
List<Employee> list = employeeMapper.selectEmployeeLikeName(employee);
System.out.println(list);
}