一、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);
}