MyBatis复杂映射
上次课程中实现的Sql语句都是比较简单的
简单sql语句可以直接使用注解编写在接口中的方法上
但是如果sql语句比较复杂,注解的方式功能就比较有限了
需要使用Xml文件来支持功能更强大的查询
在mapper.xml文件中 “<=” 与 “<” 与 “>=” 与 “>” 的书写方式
Mybatis中的sql语句中的“<”和“>”号要用转义字符“<”和”>“,不能直接写"<“或”>",否则会报错!
示例:
小于➡"<“➡”<"
大于➡">“➡”>"
小于等于➡"<=“➡”<="
大于等于➡">=“➡”>="
使用Xml文件来映射sql语句
步骤1:
实现Xml映射的sql语句也是在接口中编写一个方法
但是这个方法不需要加注解
public interface UserMapper {
//xml映射sql语句的方法
//不需要在方法上写注解
String getUsernameById();
///......
}
步骤2:
在resources文件夹下新建一个mappers的文件夹
其中新建一个UserMapper.xml文件
代码如下:
<?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">
<!-- namespace 属性执行映射到 UserMapper接口 -->
<mapper namespace="cn.tedu.mapper.UserMapper">
<!-- 需要将返回值类型声明给SqlSessionFactory -->
<!-- 使用resultType指定类型,类型需要使用全类名表示 -->
<select id="getUsernameById" resultType="java.lang.String">
select username from t_user where id=1
</select>
</mapper>
步骤3:
将我们配置xml文件的路径记录在jdbc.properties文件中,以便配置类使用
db.driver=com.mysql.cj.jdbc.Driver
db.url=jdbc:mysql://localhost:3306/tedu_ums?characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true
db.username=root
db.password=root
db.maxActive=10
db.initialSize=2
#新增的配置!!!!!!!!
mybatis.mapper.location=classpath:mappers/*.xml
步骤4:
在MybatisConfig类中修改对SqlSessionFactory类的注入
@Bean
public SqlSessionFactory sqlSessionFactory(
DataSource dataSource,
//使用@Value指定xml文件的位置,并赋值给Resource数组
@Value("${mybatis.mapper.location}")
Resource[] mapperLocations
) throws Exception {
SqlSessionFactoryBean bean=
new SqlSessionFactoryBean();
bean.setDataSource(dataSource);
bean.setMapperLocations(mapperLocations);
return bean.getObject();
}
步骤5:
测试
@Test
public void testHelloXml(){
UserMapper mapper=ctx.getBean(
"userMapper",UserMapper.class);
String username=mapper.getUsernameById();
System.out.println(username);
}
方法的可变参数
可变参数方法的定义
//可变参数方法的定义
//在参数列表中参数类型后加...表示这个参数是可变参数
//1.一个方法只能有一个可变参数,而且必须是这个方法的最后一个参数
//2.在方法内部处理这个参数时,将这个参数视为一个数组
public static void sum(int... nums){
//假设这个方法的业务是计算所有参数之和
int sum=0;
for(int i=0;i<nums.length;i++){
sum+=nums[i];
}
System.out.println(sum);
}
可变参数方法的调用
public static void main(String[] args) {
//可变参数方法调用事项
//1.可变参数的数量可以是0个,1个或多个
//2.可变参数的位置可以传入该类型的数组
sum();
sum(100);
sum(8,10,20);
int[] a={5,9,1,2,4,2,3,7};
sum(a);
}
使用Xml处理动态Sql语句
动态foreach
如果现在要一次性根据id删除多条记录
怎么操作?
如果使用按id删除一条记录的方法来遍历删除,由于连库次数多,删除效率一定低
我们可以使用动态sql语句的foreach来动态生成SQL语句删除记录
实现一条sql语句删除多行:
delete from t_user where id in (?,?,?)
直接使用可变参数指定要删除的id
UserMapper方法定义如下
//动态foreach删除
Integer deleteById(Integer... ids);
xml文件中代码如下
<!-- 动态foreach删除 -->
<delete id="deleteById">
delete from t_user where id in (
<!-- array就是数组类型参数,当参数中只有唯一数组类型时,自动赋值 -->
<!-- item表示从数组\集合中获取出的元素 -->
<!-- separator是多个元素之间的分隔符-->
<foreach collection="array" item="id" separator=",">
#{id}
</foreach>
)
</delete>
<foreach>标签还有两个属性
分别是open和close
表示循环开始之前和结束之后加什么内容
显示运行\生成的sql语句
我们希望能够看到我们自己编写的sql运行的状态
所以我们可以利用log4j日志记录的功能将Mybatis运行的过程输出到控制台
步骤1:
首先保证有log4j的依赖
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.25</version>
</dependency>
步骤2:在resources文件夹在新增log4j.properties配置文件
log4j.rootLogger=ERROR, stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n
# MyBatis Mapper
log4j.logger.cn.tedu.mapper=TRACE
动态分支结构
在Mybatis的Xml文件中除了可以进行循环
还可以进行分支结构
即当满足指定条件时添加指定sql语句
实现方式有if标签和choose标签
<if test="表达式">
sql语句片段
</if>
Mybaits中没有支持if标签的else标签只能手动编写条件表达式的反表达式
如果非要类似else标签的支持,可以选用choose标签
<choose>
<when test="表达式">
当条件满足时生成的sql语句片段
</when>
<!-- 允许编写多个when标签 -->
<otherwise>
当所有when表达式都没有满足时生成的sql片段
</otherwise>
</choose>
下面我们来实现几个具体的业务
回忆一下我们上次课讲的全行修改
UPDATE t_user
SET username=#{username},
password=#{password},
age=#{age},
phone=#{phone},
email=#{email}
WHERE id=#{id}
全行修改这个业务实际开发中几乎不会用到
但是对其中某一个列修改的业务又非常常见
我们不希望为买个列的单独修改都编写一个方法,所以希望编写一个方法
根据参数的情况,来修改不同的列
在UserMapper接口中编写代码如下
//动态 if 修改
Integer updateUserInfo(User user);
在UserMapper.xml文件中编写
<update id="updateUserInfo" parameterType="cn.tedu.entity.User">
UPDATE t_user
<!-- set标签会生成set关键字,还会自动去掉动态if部分产生的最后一个, -->
<set>
<!-- 当某个属性不为空时,表示要修改这个属性的值 -->
<if test="username != null">
username=#{username},
</if>
<if test="password !=null">
password=#{password},
</if>
<if test="age != null">
age=#{age},
</if>
<if test="phone != null">
phone=#{phone},
</if>
<if test="email != null">
email=#{email}
</if>
</set>
WHERE id=#{id}
</update>
测试一下
@Test
public void testUpdate(){
UserMapper mapper=ctx.getBean(
"userMapper",UserMapper.class);
User user=new User();
user.setId(10);
//user.setEmail("frank02@qq.com");
//user.setAge(28);
user.setPassword("4321");
user.setPhone("18055500555");
Integer num=mapper.updateUserInfo(user);
System.out.println(num);
}
动态参数查询
我们能经常看到下面的查询界面
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-izopQF9R-1607340542978)(pvboxAhOLtSVXjRk.png!thumbnail)]
如果三个条一个都不写直接点查询:
执行
select * from t_user
如果写其中的一个条件点查询:
可能
select * from t_user where username like ?
select * from t_user where age=?
select * from t_user where phone like ?
如果写了其中两个或更多条件则与运行
select * from t_user where username like ? and age=?
.....
显然这个又一种动态sql语句拼接的情况
首先编写接口方法
//动态id 查询
List<User> findUsersByParam(
@Param("username")String username,
@Param("age")Integer age,
@Param("phone") String phone
);
UserMapper.xml文件中
<!-- 动态查询 -->
<select id="findUsersByParam" resultType="cn.tedu.entity.User">
select
id,username,password,age,phone,email
from
t_user
<where>
<if test="username != null">
username like #{username}
</if>
<if test="age != null">
and age = #{age}
</if>
<if test="phone != null">
and phone like #{phone}
</if>
</where>
</select>
测试代码
@Test
public void testSelect(){
UserMapper mapper=ctx.getBean(
"userMapper",UserMapper.class);
List<User> list=mapper.findUsersByParam(
"F%",null,"13%");
for(User u: list){
System.out.println(u);
}
}
<?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">
<!-- namespace 属性执行映射到 UserMapper接口 -->
<mapper namespace="cn.tedu.mapper.UserMapper">
<!--需要将返回值类型声明给SqlSessionFactory-->
<!--使用resultType来指定类型,类型需要使用全类名表示 -->
<select id="getUsernameById" resultType="java.lang.String">
select username from t_user where id=1
</select>
<delete id="deleteByIds">
delete from t_user where id in (
<!--array就是数组类型的参数,当参数只有唯一数组类型时,自动赋值-->
<!--item表示从数组/集合中获取出的元素,名字自定义-->
<!--separator是多个元素之间的分隔符-->
<foreach collection="array" item="id" separator=",">
#{id}
</foreach>
)
</delete>
<update id="updateUserInfo" parameterType="cn.tedu.entity.User">
update t_user
<!--set标签会生成set关键字,还会自动去掉动态if部分产生的最后一个逗号","-->
<set>
<!--当某个属性不为空时,表示要修改这个属性的值-->
<if test="username !=null">
username=#{username},
</if>
<if test="password !=null">
password=#{password},
</if>
<if test="age !=null">
age=#{age},
</if>
<if test="phone !=null">
phone=#{phone},
</if>
<if test="email !=null">
email=#{email}
</if>
</set>
where
id=#{id}
</update>
<!-- 动态查询 -->
<select id="findUserByParam" resultType="cn.tedu.entity.User">
select
id,username,password,age,phone,email
from
t_user
<where>
<if test="username !=null">
username like #{username}
</if>
<if test="age !=null">
and age like #{age}
</if>
<if test="phone !=null">
and phone like #{phone}
</if>
</where>
</select>
<select id="findUserDepartment" resultType="cn.tedu.vo.UserVO">
select
u.id,
username,
d.name as departmentname
from
t_user u
left join
t_department d
on
u.department_id=d.id
</select>
</mapper>
表关系基础
数据库中的多张表可能存在关联关系
比如
公司部门表(t_department)和员工表(t_user)
-
一个部门有多个员工,一个员工只能属于一个部门
这样情况就叫一对多(多对一)
上面的情况是数据库中最常见的表关系
-
一个人只有一张身份证和一份档案
一张身份证只能对应一份档案
一份档案中只保存一个身份信息
这种情况就是一对一
3.在中小学校中,老师和学生的关系
一个老师教多个学生
一个学生被多个老师教
上面的情况就是多对多
Mybatis实现关联查询
关联查询准备工作
我们创建一个部门表,再利用现有的t_user表形成一个一对多的关系
-- 创建 t_department 表
CREATE TABLE t_department (
id INT(11) AUTO_INCREMENT COMMENT '部门ID',
name VARCHAR(20) NOT NULL COMMENT '部门名',
PRIMARY KEY (id)
)DEFAULT CHARSET=utf8;
为表赋值
-- 在t_department表中插入数据
INSERT INTO t_department (name) VALUES ('Java'), ('C++'), ('Linux');
为t_user表新加一个部门id列
alter table t_user add column department_id int
为新加的列赋值
UPDATE t_user SET department_id=1 WHERE id<=7;
UPDATE t_user SET department_id=2 WHERE id>7;
添加外键(可选)
ALTER TABLE t_user
ADD CONSTRAINT dept_user FOREIGN KEY (department_id)
REFERENCES t_department(id)
使用值对象(vo)处理关联查询
现在需求查询如下信息
查询上面呃信息需要如下sql语句
SELECT u.id,username,d.name
FROM t_user u
LEFT JOIN t_department d
ON u.department_id=d.id
这样的查询结果,我们没有合适的实体类能接收
简单来讲我们可以为了这次查询创建一个合适的实体类
这种实体类称之为Vo(值对象)
创建cn.tedu.vo包,包中新建UserVO类
public class UserVO implements Serializable {
private Integer id;
private String username;
private String departmentName;
// 省略get\set\toString
}
UserMapper接口新加方法
//关联查询(值对象查询)
List<UserVO> findUserDepartment();
UserMapper.xml文件
<select id="findUserDepartment" resultType="cn.tedu.vo.UserVO">
SELECT
u.id,
username,
d.name departmentname
FROM
t_user u
LEFT JOIN
t_department d
ON
u.department_id=d.id
</select>
测试类代码
// 关联查询(值对象)
@Test
public void getUserVO(){
UserMapper mapper=ctx.getBean(
"userMapper",UserMapper.class);
List<UserVO> list=mapper.findUserDepartment();
for(UserVO vo :list){
System.out.println(vo);
}
}
使用一的一方处理关联查询
下面我们如果想查询出如下结果
一张表的id和name,另一张表的对象的属性
我们先创建cn.tedu.entity.Department
public class Department implements Serializable {
private Integer id;
private String name;
private List<User> users;
//省略get\set\toString
}
我们要执行的sql语句如下
按部门id查询这个部门信息和这个部门的所有员工
SELECT *
FROM t_department d
LEFT JOIN t_user u
ON d.id=u.department_id
WHERE d.id=1
由于下面的操作针对的是Department类,所以应该创建Department对应的Mapper和xml
创建DepartmentMapper接口,编写代码如下
public interface DepartmentMapper {
//按id查询部门以及部门员工的信息
Department findDeptWithUserById(Integer id);
}
创建DepartmentMapper.xml文件
<?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.tedu.mapper.DepartmentMapper">
<!-- 下面是一个resultMap的自定义映射 -->
<!--可以编写一个\多个查询结果中数据查询的列名对应java的属性名的关系 -->
<resultMap id="deptMap" type="cn.tedu.entity.Department">
<!-- 每个result标签表示一个列和一个java属性的对应关系 -->
<result column="id" property="id"/>
<result column="name" property="name"/>
<!-- 当实体类中有List或类似集合类型对象时 -->
<!-- 就使用collection来映射 -->
<!-- ofType表明集合的泛型类型,property就是属性名 -->
<collection property="users" ofType="cn.tedu.entity.User">
<!--下面的映射对应的是User类的属性-->
<result column="userId" property="id" />
<result column="username" property="username"/>
<result column="password" property="password"/>
<result column="age" property="age"/>
<result column="phone" property="phone"/>
<result column="email" property="email"/>
</collection>
</resultMap>
<select id="findDeptWithUserById" resultMap="deptMap">
SELECT
d.id,
name,
u.id userId,
username,
password,
age,
phone,
email
FROM t_department d
LEFT JOIN t_user u
ON d.id=u.department_id
WHERE d.id=#{id}
</select>
</mapper>
测试
//关联查询(映射集合)
@Test
public void selectList(){
DepartmentMapper mapper=ctx.getBean(
"departmentMapper",DepartmentMapper.class);
Department d=mapper.findDeptWithUserById(1);
System.out.println(d);
}