SSM之MyBatis 03 —— 使用注解开发、Lombok、多对一&一对多处理
八、使用注解开发8.1、面向接口编程我们都学过面向对象编程,也学过接口,但在真正的开发中,我们很多时候都是选择面向接口编程。根本原因:解耦、可拓展、提高复用、分层开发,上层不用管具体的实现,大家都遵守共同的标准,使得开发变得更容易,规范性更好。在面向对象的系统中,系统的各种功能是由许多不同对象协作完成的,各个对象内部具体是如何实现的,这对于系统设计者来说不那么重要。而各个对象间的协作关系是系统设计
系列文章
SSM之MyBatis 01 —— 第一个MyBatis程序、增删改查(模糊查询)
SSM之MyBatis 02 —— 配置文件说明、日志工厂、分页(Limit和RowBounds)
SSM之MyBatis 03 —— 使用注解开发、Lombok、多对一&一对多处理
SSM之MyBatis 04 —— 动态SQL、缓存Cache
文章目录
八、使用注解开发
8.1、面向接口编程
- 我们都学过面向对象编程,也学过接口,但在真正的开发中,我们很多时候都是选择面向接口编程。
- 根本原因:解耦、可拓展、提高复用、分层开发,上层不用管具体的实现,大家都遵守共同的标准,使得开发变得更容易,规范性更好。
- 在面向对象的系统中,系统的各种功能是由许多不同对象协作完成的,各个对象内部具体是如何实现的,这对于系统设计者来说不那么重要。
- 而各个对象间的协作关系是系统设计的关键,不同类的通信、各模块间的交互,这些在系统设计之初就得考虑清楚,面向接口编程就是按照这种思想。
关于接口的理解
- 接口是定义(规范、约束)与实现的分离
- 接口本身反映了系统设计人员对系统的抽象理解
- 接口分为两类:
- 个体的抽象,对应于一个抽象体(abstract class)
- 一个个体某一方面的抽象,形成一个抽象面(interface)
- 一个个体可能有多个抽象面,抽象体和抽象面是有区别的
三个面向的区别
- 面向对象:我们考虑问题时,以对象为单位,考虑它的属性和方法。
- 面向过程:我们考虑问题时,以一个具体的流程为单位,考虑它的实现。
- 接口设计与非接口设计是针对复用技术而言的,与面向对象(过程)不是一个问题,更多的体系是对系统整体的架构。
8.2、注解开发
本质:反射机制
我另外有讲注解与反射的博客:Java 注解与反射 01 —— 注解、Java 注解与反射 02 —— 反射
底层:动态代理(前面Java基础的多线程讲过,后面Spring也会详细讲)
1、注解在接口上实现
package com.zcy.dao;
import com.zcy.pojo.User;
import org.apache.ibatis.annotations.Select;
import java.util.List;
public interface UserMapper {
@Select("select * from user")
public List<User> getAllUser();
}
2、在核心配置文件中绑定接口
注意这里是用的class方式,因为用注解就不需要用mapper.xml,也就没用resource方式。
<mappers>
<mapper class="com.zcy.dao.UserMapper"/>
</mappers>
3、测试
package dao;
import com.zcy.dao.UserMapper;
import com.zcy.pojo.User;
import com.zcy.utils.MyBatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
import java.util.List;
public class UserMapperTest {
@Test
public void getAllUser(){
SqlSession sqlSession = MyBatisUtils.getSqlSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
List<User> userList = userMapper.getAllUser();
for (User user : userList)
System.out.println(user);
sqlSession.close();
}
}
结果:
可以发现,由于实体类User的成员变量和数据库中user表的字段名不一致,导致username和pwd为空,而使用注解方式显然不能用resultMap来解决,官方也说明了注解方式可以让代码更加简介,但那是在比较简单的场景下,大多数复杂情况使用注解只会更加麻烦。
MyBatis详细执行流程(了解)
public class MyBatisUtils {
private static SqlSessionFactory sqlSessionFactory;
static {
String resource = "mybatis-config.xml";
try {
InputStream inputStream = Resources.getResourceAsStream(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
} catch (IOException e) {
e.printStackTrace();
}
}
public static SqlSession getSqlSession(){
return sqlSessionFactory.openSession();
}
}
在MyBatisUtils工具类中,通过将文件mybatis-config.xml转为流,然后调用SqlSessionFactoryBuilder的build方法,而在build方法中会调用重载的build,将环境、配置文件等作为XMLConfigBuilder对象封装好,最后在Configuration对象里。
接着才是我们看见的,得到SqlSessionFactory对象,而在我们得到SqlSession对象前,还有事务以及执行器。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UyAQbbmk-1614038856245)(MyBatis2.assets/1613979082099.png)]
8.3、注解CRUD
关于@Param()注解
- 基本类型的参数或者String类型,需要加上
- 引用类型不需要加
- 如果只有一个基本类型,可以忽略,但建议也加
- 在SQL中引用的变量就是@Param中设定的属性名
1、编写接口,增加注解,注意基本类型的参数需要添加Param注解
package com.zcy.dao;
import com.zcy.pojo.User;
import org.apache.ibatis.annotations.*;
import java.util.List;
public interface UserMapper {
@Select("select * from user")
public List<User> getAllUser();
//如果是基本类型的参数,前面需要用Param注解,注解里的值就是SQL中引用的变量
@Delete("delete from user where id = #{uid}")
public int deleteUser(@Param("uid") int id);
@Insert("insert into user (id, name, password) values(#{id}, #{username}, #{pwd})")
public int addUser(User user);
@Update("update user set name=#{username}, password=#{pwd} where id=#{id}")
public int updateUser(User user);
}
2、测试(之前已经注册过接口了)
@Test
public void updateUser(){
SqlSession sqlSession = MyBatisUtils.getSqlSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
User user = new User(4, "小天", "10101010");
userMapper.updateUser(user);
sqlSession.close();
}
结果:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fVAoahqj-1614038856250)(MyBatis2.assets/1613980400352.png)]
九、Lombok
当我们写POJO,即实体类的时候,常常会写大量毫无营养的代码,例如get、set、toString等等,而Lombok可以使我们通过注解方式跳过这种步骤。
优点:
- 代码更加简洁,无需自己去写构造方法、get/set等,提高一定效率
- 属性做出修改时,也简化了维护这些属性所生成的get/set方法等
缺点:
- 不支持多种参数构造器的重载(当然,用了Lombok后依然可以自己写重载构造方法)
- 降低了源代码可读性、完整性
1、IDEA安装Lombok插件
2、maven导入依赖(或者自己导入jar包)
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
<scope>provided</scope>
</dependency>
3、常用注解说明
- @NonNull:写在方法的参数前面,自动对该参数进行非空校验,为空则抛出NullPointerException
- @Getter and @Setter:用在属性上,这样无需自己写set和get方法,还能指定访问范围
- @ToString:用在类上,可自动覆盖toString方法
- @EqualsAndHashCode:用在类上,自动生成equals方法和hashCode方法
- @NoArgsConstructor、@AllArgsConstructor:用在类上,自动生成无参/所有参数的构造方法
- @Data:用在类上,相当于同时使用了@ToString、@EqualsAndHashCode、@Getter、@Setter、和@RequiredArgsConstructor,对实体类非常有用
- @Value:用在类上,是@Data的不可变形式,相当于给属性添加final声明,只提供get方法而不提供set
- @SneakyThrows:自动抛受检异常,而无需显示在方法上使用throws语句
- @Synchronized:用在方法上,将方法声明为同步的,并自动加锁
- 更多的请百度
4、使用示例,修改User类
如果用了@Data注解,就不会包含有参构造方法,而加上有参构造注解,就会覆盖无参注解,所以需要都写上,才能同时有有参和无参构造方法。
十、多对一处理
- 关联:对于学生而言,多个学生关联一个老师(多对一)
- 集合:对于老师而言,一个老师集合了很多学生(一对多)
数据库中的多对一,我们通常有两种方式:联表查询或者子查询。这里也是两种方式,如果我们仅仅直接查询学生或者老师,那么它们的属性类型是对象的值都会为null,这是由属性名和字段不一致造成的。因此解决方法都需要利用到结果集。
现在创建老师、学生表:
CREATE TABLE `teacher` (
`id` INT(10) NOT NULL,
`name` VARCHAR(30) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8;
INSERT INTO teacher(`id`, `name`) VALUES (1, '秦老师');
CREATE TABLE `student` (
`id` INT(10) NOT NULL,
`name` VARCHAR(30) DEFAULT NULL,
`tid` INT(10) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `fktid` (`tid`),
CONSTRAINT `fktid` FOREIGN KEY (`tid`) REFERENCES `teacher` (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8;
INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('1', '小明', '1');
INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('2', '小红', '1');
INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('3', '小张', '1');
INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('4', '小李', '1');
INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('5', '小王', '1');
10.1、测试环境搭建
新建一个模块,先搭建好基本的测试环境(db.properties、mybatis-config.xml)。
1、编写MyBatis工具类,并导入Lombok,方便后面简化实体类编写
2、新建实体类 Teacher、Student
//多对一:每个学生有一个老师
@Data
public class Student {
private int id;
private String name;
//这里不用tid,这是Java 组合,用Teacher更好将学生和老师绑定
private Teacher teacher;
}
//一对多:一个老师会有多个学生
@Data
public class Teacher {
private int id;
private String name;
private List<Student> students;
}
3、新建接口
package com.zcy.dao;
import com.zcy.pojo.Student;
import java.util.List;
public interface StudentMapper {
//按照查询嵌套处理
public List<Student> getStudent1();
//按照结果嵌套处理
public List<Student> getStudent2();
}
4、新建StudentMapper.xml
5、通过mybatis-config.xml注册mapper
6、测试
10.2、按照查询嵌套处理
查出每个学生以及他们每人对应的老师,这就需要联表查询(查两个表),光靠以之前学的方法是无法完成的。
<?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.zcy.dao.StudentMapper">
<!-- 思路:1. 先查初所有学生信息 2. 根据查出来的学生的tid,寻找对应的老师!这里利用了子查询 -->
<!-- 查出所有学生,学生Student的结果集是StudentTeacher -->
<select id="getStudent1" resultMap="StudentTeacher">
select * from student
</select>
<!-- getStudent1的返回值最终是Student,这里用了嵌套子查询 -->
<resultMap id="StudentTeacher" type="Student">
<result property="id" column="id"/>
<result property="name" column="name"/>
<!-- 复杂的属性需要单独处理,对象:association,集合:collection -->
<!-- 因为teacher是对象,需要指定其对象类型,它对应数据库的tid字段,将tid作为getTeacher的变量 -->
<association property="teacher" javaType="Teacher" column="tid" select="getTeacher"/>
</resultMap>
<!-- 根据结果集StudentTeacher传的参数 tid 查询老师-->
<select id="getTeacher" resultType="Teacher">
<!-- 这里不一定写成tid,因为只传一个参数,随便写一个,MyBatis会自动推断是tid -->
select * from teacher where id = #{tid}
</select>
</mapper>
10.3、按照结果嵌套处理
<!-- 方式二 -->
<!-- 按照正常的SQL写,同时指定结果集,因为返回类型肯定不是一个纯粹的Student -->
<select id="getStudent2" resultMap="StudentTeacher2">
select s.id sid, s.name sname, t.name tname
from student s, teacher t
where s.tid = t.id
</select>
<!-- 结果集就按照Student类对应着写 Student的属性 id、name、teacher -->
<resultMap id="StudentTeacher2" type="Student">
<!-- 基础类型就对应着写 id、name -->
<result property="id" column="id"/>
<result property="name" column="name"/>
<!-- 特殊类型就单独再写,teacher是一个对象,Teacher类型(如果是集合又会不一样) -->
<association property="teacher" javaType="Teacher">
<!-- 根据数据库查询的返回结果,将t.name指定别名为tname,所以Teacher中column需要对应 -->
<result property="name" column="tname"/>
</association>
</resultMap>
十一、一对多处理
11.1、测试环境搭建
大部分和多对一的环境一样,实体类、MyBatis工具类等等…
1、接口编写
public interface TeacherMapper {
//基本类型,指定参数。后面在TeacherMapper.xml中也就使用tid为参数
public Teacher getTeacherById1(@Param("tid") int id);
public Teacher getTeacherById2(@Param("tid") int id);
}
2、新建TeacherMapper.xml
3、通过mybatis-config.xml注册mapper
11.2、按照结果嵌套处理
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Maper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.zcy.dao.TeacherMapper">
<!-- 按结果嵌套查询 -->
<select id="getTeacherById1" resultMap="TeacherStudent1">
select t.id tid, t.name tname, s.id sid, s.name sname
from teacher t, student s
where t.id = s.tid
</select>
<resultMap id="TeacherStudent1" type="Teacher">
<result property="id" column="tid"/>
<result property="name" column="tname"/>
<!-- 由于students是集合List<Student>,所以用collection,同时不用JavaType而是ofType,指定泛型的约束类型-->
<collection property="students" ofType="Student">
<result property="id" column="sid"/>
<result property="name" column="sname"/>
</collection>
</resultMap>
</mapper>
11.3、按查询嵌套处理
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Maper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.zcy.dao.TeacherMapper">
<!-- 按查询嵌套处理 -->
<!-- 查出所有老师,将结果作为结果集给第二个查询。这里的tid是@Param指定的名称 -->
<select id="getTeacherById2" resultMap="TeacherStudent2">
select * from teacher where id = #{tid}
</select>
<!--如果用嵌套子查询,则需要指定javaType。因为是集合,所以用collection -->
<resultMap id="TeacherStudent2" type="Teacher">
<collection property="students" javaType="ArrayList" ofType="Student" select="getTeacherById"/>
</resultMap>
<!-- 接收结果集TeacherStudent2传的参数 tid(随便命名,因为参数只有一个),并根据tid查询student -->
<select id="getTeacherById" resultType="Student">
select * from student where tid = #{tid}
</select>
</mapper>
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)