Mybatis 如何使用通用mapper(tk.mapper)?如何使用注解简化mybatis的开发(舍弃大量的xml文件)?注解处理一对一、一对多、多对多的关系?
背景
记得在学习SSM的时候,mybatis介绍了使用逆向工程来简化数据库访问的实现,当时觉得非常棒,节省了很多时间。单表查询操作,几乎不用自己再写Dao层的方法。
直到学习到SpringBoot的时候,发现了通用mapper(tk.mapper),这个更棒了,不需要自己实现逆向工程,所有的事情jar包已经帮你做好了。
使用通用mapper(tk.mapper)比使用逆向工程(org.mapper)的好处在于:
- 每次更改数据库后,不需要再频繁的进行逆向工程了,大大的降低了容错率,提高开发效率
- 舍弃了大量的xml文件,使整个项目的架构更加清晰,当然操作也更加方便
- 如果只是单表操作,mapper接口只需继承通用mapper就好,无需其他任何方法;多表操作使用注解开发即可
- …还有更多的好处等你来发掘
但是网上搜索很多的资料基本都是使用SpringBoot来实现的,使用SSM搭建的不多,也不全,在这里总结一下
所需maven依赖
<!-- javaBean类使用jpa注解 -->
<dependency>
<groupId>javax.persistence</groupId>
<artifactId>persistence-api</artifactId>
<version>1.0</version>
</dependency>
<!-- 通用mapper -->
<dependency>
<groupId>tk.mybatis</groupId>
<artifactId>mapper</artifactId>
<version>4.1.5</version>
</dependency>
mybatis对于通用mapper的配置
可以看出与平常的配置,也只是改变了bean的calss,将org.mybatis.spring.mapper.MapperScannerConfigurer改为了tk.mybatis.spring.mapper.MapperScannerConfigurer
<!--配置生产SqlSession对象的工厂-->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<!--扫描pojo包,给包下所有pojo对象起别名-->
<property name="typeAliasesPackage" value="com.marchsoft.domain"/>
</bean>
<!-- 配置普通mapper -->
<!--扫描接口包路径,生成包下所有接口的代理对象,并且放入spring容器中-->
<!-- <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">-->
<!-- <property name="basePackage" value="com.marchsoft.dao"/>-->
<!-- </bean>-->
<!-- 配置通用mapper -->
<bean class="tk.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
<property name="basePackage" value="com.marchsoft.dao"/>
</bean>
表的映射类
映射类中加@Entity、@Table(name=“account_pass”)、@Data可以实现基本的get、set等方法
详情参看lombok包
举例
import lombok.Data;
import javax.persistence.*;
import java.io.Serializable;
@Entity
@Table(name="account_pass")
@Data
public class AccountPass implements Serializable {
/**
* 主键由数据库自动生成(主要是自动增长型)
*/
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer zid;
private Integer status;
private String account;
private String password;
}
构建通用的mapper,我们只需要继承接口就好,不需要其余多余的操作
封装通用的CommentMapper接口,方便我们继承
之所以使用这个类,是为了使我们之后写的接口可以继承多个不同的Mapper接口,这些mapper都有不同的作用,例如InsertListMapper就可以实现批量插入数据…
import tk.mybatis.mapper.additional.idlist.IdListMapper;
import tk.mybatis.mapper.additional.insert.InsertListMapper;
import tk.mybatis.mapper.annotation.RegisterMapper;
import tk.mybatis.mapper.common.Mapper;
/**
* 自定义的mapper
* ‘@RegisterMapper’ 使自定义的mapper可以被扫描到
* @author rsw
*/
@RegisterMapper
public interface CommentMapper<T> extends Mapper<T>, IdListMapper<T,Long>, InsertListMapper<T> {
}
使我们的mapper继承自定义的通用mapper类
import com.marchsoft.common.mapper.CommentMapper;
import com.marchsoft.domain.AccountPass;
import org.springframework.stereotype.Component;
@Component
public interface AccountPassMapper extends CommentMapper<AccountPass> {
}
如果我们只需要最基础的功能也可以只继承Mapper
@Component
public interface AccountPassMapper extends Mapper<AccountPass> {
}
通用mapper的使用方法
它的使用方法和逆向工程的mapper使用方法类似
之后controller调用即可正常使用
controller方法略
舍弃了大量的xml文件和mapper中以及实体扩展类的方法
下面介绍如何使用注解进行多表操作
- @Insert、@Delete、@Update、@Select对应增删改查操作,在("")中写sql语句
- @Results是追加其他的条件,常用的id表示主键,column表示数据库中的字段,property标识Javabean中对应的属性
- 若是默认的属性与数据库字段不一致,可使用其解决冲突问题
- 若果两张表有字段重复,可以使用起别名的方式,将别名写在column中与属性值对应
- 若是数据库的字段是关键字,实行会报sql异常,可以给字段加``,例如
group->`group`
操作实例:
@Select("SELECT * FROM teacher WHERE group_id=#{gid}")
@Results({
@Result(id=true,column="group_id",property="groupId"),
})
List<Teacher> queryTeacherInfo(Integer gid);
当然了,有时候我们进行多表操作只是使用这样的方式并不能满足我们的需求。像方法的返回值这样就定死了,而且mybatis中也讲述了如何实现一对一、一对多、多对多的查询。
下面讲述如何封装vo扩展类和使用注解实现一对一、一对多、多对多的查询
注解实现一对一查询
Student表
private Integer sid;
private String name;
//小组id(存放group的主键id)
private Integer groupId;
Group表
private Integer id;
private String name;
封装学生的vo扩展类StudentExtra
一般来说可以封装成vo扩展类,使其结构更加清晰
public class StudentExtra extends Student {
/**
* 学生所加入的小组信息
*/
private GroupExtra groupExtra;
}
如果不想封装vo类也可以在student中封装字段
//'@Transient' 表示不是数据库的字段
@Transient
private GroupExtra groupExtra;
StudentMapper
/**
* 查询学生信息(附带兴趣小组)
* @param sid 学生id
* @return 学生信息(附带兴趣小组)
*/
@Select("SELECT s.*,g.* FROM student s LEFT JOIN `group` g ON s.tid=g.tid WHERE g.zid=s.zid AND s.sid=#{sid}")
@Results({
@Result(column="group_id",property="groupId"),
//这里的column="group_id"是将列值的参数传给GroupMapper的queryGroupByGid方法
@Result(column="group_id",property="groupExtra",
one=@One(select="com.marchsoft.dao.GroupMapper.queryGroupByGid",
fetchType= FetchType.EAGER)),
})
StudentExtra queryStudentInfoWithTeacher(Integer sid);
GroupMapper
/**
* 根据小组id查询其信息(包含老师信息)
* @param gid 小组id,StudentMapper出来的值
* @return 一条小组信息
*/
@Select("SELECT * FROM `group` WHERE id=#{gid}")
@Results({
@Result(id=true,column="id",property="id"),
})
Group queryGroupByGid(Integer gid);
如此就是实现了一对一的查询,可以在service中直接调用
@Override
public StudentExtra queryStudentInfoWithTeacher(Integer sid){
return studentMapper.queryStudentInfoWithTeacher(sid);
}
注:从上面的实例可以看出,想要实现一对一的关系,那么一表中就必须要有另一张表的依赖关系。**上边实例中,student中有group的id,groupId与g.id一一对应才可实现。**若是没有就要采用普通的连表查询了,将所需要的字段封装在vo的扩展类当中
实例
public class StudentExtra extends Student {
/**
* 学生的学号(account_pass表)
*/
private String account;
/**
* 教师的姓名(teacher表)
*/
private String teacherName;
/**
* 教师师邮箱(teacher表)
*/
private String email;
/**
* 导师简介(teacher表)
*/
private String introduction;
/**
* 导师所带专业(teacher表)
*/
private String major;
}
StudentMapper
@Select("SELECT s.*,t.* FROM student s,teacher t WHERE s.tid=t.tid")
@Results({
@Result(column="name",property="name"),
@Result(column="group_id",property="groupId"),
@Result(column="teacher_name",property="teacherName"),
@Result(column="class_name",property="className"),
})
StudentExtra queryStudentInfo();
注解实现一对多查询
这里 我拿省份(Provinces)和城市(Citys)拿来做实列
Provinces表
private int pid;
private String pname;
private Set<Citys> citysSet;
Citys表
private int cid;
private String cname;
//对应Provinces表的主键id
private int pid;
ProvincesMapper
@Select("select * from provinces where pid = #{pid}")
@Results({
@Result(id=true,column="pid",property="pid"),
@Result(column="pname",property="pname"),
@Result(column="pid",property="citysSet",
many=@Many(
select="com.desert.dao.ICityDao.getCitybypid",
fetchType= FetchType.LAZY
)
)
})
public Provinces getProvincesByid(int pid);
Citys
@Select("select * from city where pid=#{pid}")
public List<Citys> getCitybypid(int pid);
注:一表中也必须要有另一张表的依赖关系,上边的实例是pid,因为需要进行关联
之后就可以调用了
略
注解实现多对多查询
关系:一个学生有多个兴趣小组,一个兴趣小组可以有多个老师
StudentMapper
/**
* 查询学生信息(附带兴趣小组)
* @param sid 学生id
* @return 学生信息(附带兴趣小组)
*/
@Select("SELECT s.*,g.* FROM student s LEFT JOIN `group` g ON s.tid=g.tid WHERE g.zid=s.zid AND s.sid=#{sid}")
@Results({
@Result(column="group_id",property="groupId"),
//这里的column="group_id"是将列值的参数传给GroupMapper的queryGroupByGid方法
@Result(column="group_id",property="groupExtra",
many=@Many(select="com.marchsoft.dao.GroupMapper.queryGroupByGid",
fetchType= FetchType.EAGER)),
})
StudentExtra queryStudentInfoWithTeacher(Integer sid);
GroupMapper
/**
* 根据小组id查询其信息(包含老师信息)
* @param gid 小组id
* @return 一条小组信息
*/
@Select("SELECT * FROM `group` WHERE id=#{gid}")
@Results({
@Result(id=true,column="id",property="id"),
@Result(column="id",property="teacherList",
many=@Many(
select="com.marchsoft.dao.TeacherMapper.queryTeacherInfo",
fetchType= FetchType.LAZY)
)
})
GroupExtra queryGroupByGid(Integer gid);
TeacherMapper
/**
* 根据小组id查询导师
* @param gid
* @return
*/
@Select("SELECT * FROM teacher WHERE group_id=#{gid}")
@Results({
@Result(id=true,column="group_id",property="groupId"),
})
List<Teacher> queryTeacherInfo(Integer gid);
之后就可以调用了。
附@Results用法总结
MyBatis中使用@Results注解来映射查询结果集到实体类属性
(1)@Results的基本用法。当数据库字段名与实体类对应的属性名不一致时,可以使用@Results映射来将其对应起来。column为数据库字段名,porperty为实体类属性名,jdbcType为数据库字段数据类型,id为是否为主键。
@Select({"select id, name, class_id from my_student"})
@Results({
@Result(column="id", property="id", jdbcType=JdbcType.INTEGER, id=true),
@Result(column="name", property="name", jdbcType=JdbcType.VARCHAR),
@Result(column="class_id", property="classId", jdbcType=JdbcType.INTEGER)
})
List<Student> selectAll();
如上所示的数据库字段名class_id与实体类属性名classId,就通过这种方式建立了映射关系。名字相同的可以省略
(2)@ResultMap的用法。当这段@Results代码需要在多个方法用到时,为了提高代码复用性,我们可以为这个@Results注解设置id,然后使用@ResultMap注解来复用这段代码
@Select({"select id, name, class_id from my_student"})
@Results(id="studentMap", value={
@Result(column="id", property="id", jdbcType=JdbcType.INTEGER, id=true),
@Result(column="class_id", property="classId", jdbcType=JdbcType.INTEGER)
})
List<Student> selectAll();
@Select({"select id, name, class_id from my_student where id = #{id}"})
@ResultMap(value="studentMap")
Student selectById(integer id);
(3)@One的用法。当我们需要通过查询到的一个字段值作为参数,去执行另外一个方法来查询关联的内容,而且两者是一对一关系时,可以使用@One注解来便捷的实现。比如当我们需要查询学生信息以及其所属班级信息时,需要以查询到的class_id为参数,来执行ClassesMapper中的selectById方法,从而获得学生所属的班级信息。可以使用如下代码
@Select({"select id, name, class_id from my_student"})
@Results(id="studentMap", value={
@Result(column="id", property="id", jdbcType=JdbcType.INTEGER, id=true),
@Result(column="class_id", property="myClass", javaType=MyClass.class,
one=@One(select="com.example.demo.mapper.MyClassMapper.selectById"))
})
List<Student> selectAllAndClassMsg();
(4)@Many的用法。与@One类似,只不过如果使用@One查询到的结果是多行,会抛出TooManyResultException异常,这种时候应该使用的是@Many注解,实现一对多的查询。比如在需要查询学生信息和每次考试的成绩信息时。
@Select({"select id, name, class_id from my_student"})
@Results(id="studentMap", value={
@Result(column="id", property="id", jdbcType=JdbcType.INTEGER, id=true),
@Result(column="class_id", property="classId", jdbcType=JdbcType.INTEGER),
@Result(column="id", property="gradeList", javaType=List.class,
many=@Many(select="com.example.demo.mapper.GradeMapper.selectByStudentId"))
})
List<Student> selectAllAndGrade();
(5)传递多个参数。首先我们给这张表增加age(年龄)和gender(性别)两个参数。当我们需要根据age和gender查询学生的午餐,这时需要改写column属性的格式。等号左侧的age和gender对应java接口的参数,右侧的对应数据库字段名。即将查到的my_student表中age和gender字段的值,分别赋给getLunchByAgeAndGender方法中的age和gender参数,去查询对应的name(午餐名)
@Select("select id, name, age, gender from my_student")
@Results({
@Result(column="id", property="id", jdbcType=JdbcType.INTEGER, id=true),
@Result(column="class_id", property="classId", jdbcType=JdbcType.INTEGER),
@Result(column="{age=age,gender=gender}", property="lunch",
one=@One(select="com.example.demo.mapper.StudentMapper.getLunchByAgeAndGender")),
})
List<Student> selectAllAndLunch();
@Select("select name from lunch where student_age = #{age} and student_gender = #{gender}")
String getLunchByAgeAndGender(@Param("age") int age, @Param("gender") int gender);
至此完毕!