2 Mybatis常用配置
mybatis-config.xml中除了可以配置数据库服务器的环境以外,还可以配置其他的操作,帮助开发人员简化配置代码。常见的配置信息有以下内容:
1 配置内容
SqlMapConfig.xml中配置的内容和顺序如下:
properties(属性)***
settings(全局配置参数)***
typeAliases(类型别名)***
typeHandlers(类型处理器)
objectFactory(对象工厂)
plugins(插件)***
environments(环境集合属性对象)***
environment(环境子属性对象)
transactionManager(事务管理)
dataSource(数据源)
mappers(映射器)***
注意:配置时如果标签顺序错误,SqlMapConfig.xml文件将会报错
2 properties属性
Properties属性可以用来引用java属性文件中和配置如数据连接配置
MyBatis 将按照下面的顺序来加载属性:
使用 properties 元素加载的外部属性文件优先级最高。 然后会读取properties元素中resource加载的属性。
3 typeAliases
类型别名可为 Java 类型设置一个缩写名字。 它仅用于 XML 配置,意在降低冗余的全限定类名书写。
官方文档提供了两种起别名方式
为每个实体类设置一个别名
<typeAliases>
<typeAlias alias="Author" type="domain.blog.Author"/>
<typeAlias alias="Blog" type="domain.blog.Blog"/>
<typeAlias alias="Comment" type="domain.blog.Comment"/>
<typeAlias alias="Post" type="domain.blog.Post"/>
<typeAlias alias="Section" type="domain.blog.Section"/>
<typeAlias alias="Tag" type="domain.blog.Tag"/>
</typeAliases>
方案一缺点就是:一个一个指定别名,实体类较多时,代码比较麻烦。所以可以使用方案二
批量起别名
<!--
简化引用实体类时,包名.类名的冗余写法
只能给实体类起别名
-->
<typeAliases>
<!--
package将指定包下面的类都起别名,别名默认:类名首字母小写
如果,你希望用自己指定别名,不用类名首字母小写,类添加@Alias("自己的别名")
-->
<package name="com.woniu.entity"/>
</typeAliases>
当给实体类设置别名后,配置sql的xml文件中就可以通过别名来指定实体类了:
<select id="selectAll" resultType="bookInfo">
SELECT book_id,book_code,book_name,book_type,book_author,publish_press,publish_date,is_borrow FROM `book_info`
</select>
不过,偶尔也会有人想着:批量起别名的情况下,给某个特定类指定特定的别名,这个需求我们可以借助在实体类上添加@Alias的方式解决
@Alias("b") //@Alias注解会覆盖默认类名首字母小写
public class BookInfo {
private Integer bookId;
private String bookCode;
private String bookName;
private Integer bookType;
private String bookAuthor;
private String publishPress;
private Date publishDate;
private Integer isBorrow;
}
重新启动测试代码,控制台会输出以下错误:
因为一旦实体类上@Alisas指定个性化别名后,批量配置别名所设置的“类名首字母小写”的别名就被覆盖了。不能再使用“bookInfo"而是改为”b"
一般建议初学者使用全类名写法,即使开启别名,也建议使用类名首字母小写的方案,增强类名的可阅读性
4 mappers
当我们每次有新的mapper.xml文件后都需要将对应的文件在sqlmapconfig.xml中进行注册,接下来我们来学习mapper映射器的三种配置方式。
配置mapper.xml类路径
<mappers>
<!-- 扫描指定mapper.xml文件-->
<mapper resource="cn/woniu/dao/UserDao.xml"/>
</mappers>
配置mapperdao接口路径
<mappers>
<!-- 扫描指定mapper.xml -->
<!-- <mapper resource="cn/woniu/dao/UserDao.xml" /> -->
<!-- 扫描指定dao接口 -->
<mapper class="cn.woniu.dao.UserDao"/>
</mappers>
批量扫描dao接口【开发中推荐使用此方式】
上面的两种配置方式每增加一个mapper.xml或mapperdao接口都要增加一行配置,当项目较大时不便于开发,在实际开发中使用批量自动扫描包功能
<mappers>
<!-- 扫描指定mapper.xml -->
<!-- <mapper resource="cn/woniu/dao/UserDao.xml" /> -->
<!-- 扫描指定dao接口 -->
<mapper class="cn.woniu.dao.UserDao"/>
<!-- dao接口自动扫描 -->
<package name="cn.woniu.dao"/>
</mappers>
注意: 此种方法要求dao接口名称和mapper映射文件名称相同,且放在同一个目录中
5 settings
settings可以修改mybatis的一些默认行为。比如查询语句的列名和对应实体类的属性映射规则;缓存;延迟加载…
<settings>
<!-- 开启数据库a_b命名到java类aB命名自动映射 -->
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
settings配置方式使用前提:数据库列名遵循a_b命名,java实体类属性遵循aB驼峰命名
3 ResultMap用法
java程序标识符有命名规范:
变量名【包含属性名、形参名称、局部变量、全局变量】:小驼峰 首字母小写,从第二个单词开始所有的首字母大写
举例:goodsName myGoodsName
方法名:小驼峰 举例:getInfo findStudentInfo()
dao: select开始 insertxx
service:find/get/query开始
类名和接口:大驼峰
mybatis-plus代码自定生成 接口以I自定义命名 举例:IUsersDao IBuildingDao
数据库命名:缘于数据库sql不区分大小写,bookname bookName BookName
SELECT ID,STUDENTNAME,PASSWORD FROM WY_USERS
列名或表名起名字:经典命名规则:单词与单词之间下划线分割
举例:book_info book_type 列名 book_code book_author
案例1:查询所有的图书信息
数据表Book_info转换为BookInfo.java实体类
public class BookInfo {
private Integer bookId;
private String bookCode;
private String bookName;
private Integer bookType;
private String bookAuthor;
private String publishPress;
private Date publishDate;
private Integer isBorrow;
}
BookInfoDao.java添加查询方法
List<BookInfo> selectAll();
BookInfoDao.xml配置sql
<select id="selectAll" resultType="com.woniu.entity.BookInfo">
SELECT book_id,book_code,book_name,book_type,book_author,publish_press,publish_date,is_borrow FROM `book_info`
</select>
测试代码
@Test
public void test04()throws Exception {
//读取配置文件,获取SqlSession工厂对象
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(
Resources.getResourceAsStream("mybatis-config.xml")
);
//从工厂对象中获取SqlSession对象
try (SqlSession session = sqlSessionFactory.openSession(true);) {
BookInfoDao mapper = session.getMapper(BookInfoDao.class);
mapper.selectAll().forEach(System.out::println);
}
}
测试结果:
原因:mybatis中使用resultType配置sql查询结果时,默认遵循查询结果列名和实体类属性名同名,自动注入的方式。我们仔细观察BookInfo.java中的实体类属性写法,会发现和sql中列名写法“缺失”下划线,所以,结果映射不成功。如何解决呢?
数据库经典命名a_b格式映射到java实体类驼峰命名aB格式,出现自动映射的问题,解决方案:
方案一:sql查询结果列起别名
<select id="selectAll" resultType="com.woniu.entity.BookInfo">
SELECT book_id bookId,book_code bookCode,book_name booname,book_type,book_author,publish_press,publish_date,is_borrow FROM `book_info`
</select>
这种方式比较简单,但是修改代码的量可能比较大。我们可以参考第二种方案
方案二:mybatis提供的ResultMap替换默认ResultType
<!-- 开发人员配置自定义映射关系
id:唯一标识
type:要给哪个实体类指定自定义映射关系
-->
<resultMap id="myBookInfo" type="com.woniu.entity.BookInfo">
<!--
id:主要用来配置主键列,优化映射性能
-->
<id column="book_id" property="bookId"/>
<!--
result:配置普通列,非主键字段
-->
<result column="book_code" property="bookCode"/>
<result column="book_name" property="bookName" jdbcType="VARCHAR" javaType="java.lang.String"/>
</resultMap>
<select id="selectAll" resultMap="myBookInfo">
SELECT book_id,book_code,book_name,book_type,book_author,publish_press,publish_date,is_borrow FROM `book_info`
</select>
当我们配置了resultMap后,在<select 中利用resultMap=“指向myBookInfo"那么查询结果的列名就会按照myBookInfo中配置的方式来完成数据映射。这种自定义配置方案非常灵活,可操作性强。推荐使用
面试题:resultType和resultMap有什么区别?
resultType中的输出类型可以分为:基本数据类型(简单数据类型)、Pojo类型、Pojo列表类型(集合),当使用pojo类型和pojo列表类型作为输出类型的时候有个前提条件就是pojo中的属性名称必须和数据表中的字段名一致。如果我们查询出来的数据是由多张表组合而来的,或查询出来的数据有别名和pojo中的名称不对应,此时要怎样输出结果?
方式一:修改pojo类中的属性与数据表查询出来属性一致,但此种方式改动较大如果在其他地方使用了该pojo将会报错(不推荐)
方式二:使用resultMap
方案三:利用mybatis的settings配置
<settings>
<!-- 开启数据库a_b命名到java类aB命名自动映射 -->
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
settings配置方式使用前提:数据库列名遵循a_b命名,java实体类属性遵循aB驼峰命名
4 抽取SQL片段
意义:减少select语句中,重复编写列名代码。
分析mapper.xml中的代码我们发现有些sql语名被重复的使用了,能不能把这个语句封装成方法一样,哪里需要就直接调用呢。在mybatis中提供了sql片段来实现这个功能。
可以利用sql片段,将重复的部分抽取出来,减少代码冗余。使用方式如下:
5 两表关联查询
两表级联操作就是用来研究两表关联查询后,查询结果如何配置的问题。
两表的实体关系
常见的两表实体关系有以下几种:
1对1
1对多
多对多:最终都会被拆分成两个1对多
下面我们就以具体的案例进行分析。以下案例都以:Book_info和Book_Type表作为数据基础
案例分析:1对1
需求分析
需求:查询所有的图书及图书所属的分类信息
实体关系分析:因为要查出所有图书及图书分类信息,主要查询时图书信息。所以此处可以使用左外连接查询
sql:SELECT book_info.*,book_type.* FROM book_info LEFT JOIN book_type ON book_info.book_type=book_type.id
思考:查询结果是由两张表的数据组成的,这样的结果集我们要怎么映射呢?
因为编写sql语句时我们是以book_info表为基表,所以返回数据时的类型也应该是Book_Info,但是还得包含有book_type表的信息,此时可以通过在BookInfo实体类中添加一个BookType的对象属性来达到效果
修改BookInfo实体类
public class BookInfo {
private Integer bookId;
private String bookCode;
private String bookName;
private Integer bookType;
private String bookAuthor;
private String publishPress;
private Date publishDate;
private Integer isBorrow;
//图书分类的信息 1对1关系实体类体现:主查表对应实体类中添加关联查询表实体类的对象
private BookType type;
}
创建BookInfoDao接口和BookInfoDao.xml
BookDao.java配置查询方法
List<BookInfo> selectByCondition(
@Param("bookName") String bookName,
@Param("typeId") Integer typeId,
@Param("isBorrow") Integer isBorrow
);
BookInfoDao.xml配置sql,并为sql提供结果集映射关系
<!--
type: 主查表的实体类
autoMapping:开启自动映射,开启后同名的列名和属性就可以自动完成映射,不用手动result进行配置
-->
<resultMap id="BookInfoAndTypeResultMap" type="bookInfo" autoMapping="true">
<!--
column:列名,不区分大小写
property:属性,严格区分大小写
-->
<id column="book_id" property="bookId"/>
<!-- <result column="book_code" property="bookCode"/>-->
<!--
association专门用来配置单个实体类对象
property:要配置的实体类属性名
javaType:指定实体类属性所属的类型
-->
<association property="type" javaType="com.woniu.entity.BookType" autoMapping="true">
<id column="id" property="id"/>
<result column="type_name" property="typeName"/>
</association>
</resultMap>
<select id="selectByCondition" resultMap="BookInfoAndTypeResultMap">
SELECT <include refid="commonSql"/>,book_type.id,book_type.type_name
FROM `book_info` LEFT JOIN `book_type` ON book_info.book_type=book_type.id
<where>
<if test="bookName!=null and bookName!=''">
and book_name like concat('%',#{bookName},'%')
</if>
<if test="typeId!=null">
and book_type=#{typeId}
</if>
<if test="isBorrow!=null">
and is_borrow=#{isBorrow}
</if>
</where>
</select>
创建测试类
@Test
public void test04()throws Exception {
List<BookInfo> list = bookInfoDao.selectByCondition(null, null, null);
list.forEach(System.out::println);
}
观察控制台输出日志:既有图书信息,还有图书分类的信息
案例分析:1对多
需求分析
在Book_info表中我们可以看到book_type为1的分类有多个图书信息,即一个图书分类下可以有多个图书信息,此时图书分类与图书的关系为一对多。现在我们来看一下如何通过图书分类来获得与它相关的所有图书信息
修改BookType实体类
因为是通过图书分类来获取对应的图书信息,所以基表是用户表Book_Type,也就是最后返回的类型为Booktype类型。一个图书分类对应多个图书信息,BookType与BookInfo之间是一对多关系。所以要在BookType实体类中添加一个List< BookInfo>来表示一对多关系。
public class BookType {
private Integer id;
private String typeName;
//重点:1对多关系,主查表对应实体类中添加从表实体类的集合!!!
private List<BookInfo> books;
}
修改BookTypeDao接口和BookTypeDao.xml
BookTypeDao.java添加查询图书分类的方法
List<BookType> selectAllTypesAndBooks();
BookTypeDao.xml配置sql,并为查询结果指定映射结果集
<resultMap id="TypeAndBookResultMap" type="bookType" autoMapping="true">
<id column="id" property="id"/>
<!-- <result/>-->
<!-- 集合类型的属性
collection专门用来配置集合类型的属性
property:集合类型的属性名
ofType:集合的泛型类型
-->
<collection property="books" ofType="com.woniu.entity.BookInfo" autoMapping="true">
<id column="book_id" property="bookId"/>
</collection>
</resultMap>
<select id="selectAllTypesAndBooks" resultMap="TypeAndBookResultMap">
SELECT book_id bookid,book_code bookcode,book_name,book_type,book_author,publish_press,publish_date,is_borrow,
book_type.id,book_type.type_name
FROM `book_info` right JOIN `book_type` ON book_info.book_type=book_type.id
</select>
创建测试类
@Test
public void test04()throws Exception {
List<BookType> types = bookTypeDao.selectAllTypesAndBooks();
types.forEach(type -> {
log.info("{}分类下的图书有:",type.getTypeName());
System.out.println(type.getBooks());
});
}
观察控制台输出日志:既有图书分类的信息,还能看到各个分类下所有的图书信息
案例分析:多对多
需求分析
多对多关系其实是双向的一对多关系。此处我们使用用户表和角色表
从表关系中我们可以发现,用户表和角色表里面并没有对方的外键,而是专门有一张表存放两张表的外键关系。
单向一对多:查询所有的用户角色及角色下属的用户信息
a、创建Role实体类
在实际开发中,关系表一般不在项目中创建对应的实体类。我们的需求是要查询出所每个角色下所有的用户信息所以该需求最终返回的数据应该是角色类型(Role),所以Role实体类是基类。
public class Role{
//创建用户对象的集合
private List<User> users;
}
所以,本质来说:多对多就是双向一对多,双向操作参考上面1对多案例
ResultMap常用属性
-
autoMapping:开启结果集自动映射,通常在配置ResultMap时,如果查询结果集列名和映射实体类的属性名一样(不区分单词大小写的情况)可以开启自动映射:autoMapping=“true”。不过自动映射在处理多表级联时,因为映射等级的限制,效果会有差别。
autoMapping有三种自动映射等级:- NONE - 禁用自动映射。仅对手动映射的属性进行映射。
- PARTIAL - 对除在内部定义了嵌套结果映射(也就是连接的属性)以外的属性进行映射
- FULL - 自动映射所有属性。
-
extends:配置结果集时,如果多个结果集存在“相同”的配置代码,可以将这些“重复”的配置抽取成一个公共的ResultMap,然后通过继承的方式沿用。
autoMapping案例如下:
<resultMap id="BookAndTypeMap" type="bookInfo" autoMapping="true" >
<association property="type" javaType="com.woniu.entity.BookType" autoMapping="true">
<id property="id" column="id"/>
</association>
</resultMap>
<select id="selectAllBooksAndType" resultMap="BookAndTypeMap">
SELECT
book_info.book_id,
book_info.book_code,
book_info.book_name,
book_info.book_type,
book_info.book_author,
book_info.publish_press,
book_info.publish_date,
book_info.is_borrow,
book_type.id,
book_type.type_name
FROM
book_info
LEFT JOIN
book_type
ON
book_info.book_type = book_type.id
ORDER BY
book_info.book_id ASC
</select>
extend案例如下:
<resultMap id="MyBookInfoMap" type="com.woniu.entity.BookInfo">
<id property="bookId" column="book_id" javaType="java.lang.Integer" jdbcType="INTEGER"/>
<result property="bookCode" column="book_code"/>
<result property="bookName" column="book_name" javaType="java.lang.String" jdbcType="VARCHAR"/>
<result property="bookType" column="book_type"/>
<result property="bookAuthor" column="book_author"/>
<result property="publishPress" column="publish_press"/>
<result property="publishDate" column="publish_date"/>
<result property="isBorrow" column="is_borrow"/>
</resultMap>
<resultMap id="BookAndTypeMap" type="bookInfo" extends="MyBookInfoMap">
<association property="type" javaType="com.woniu.entity.BookType">
<!-- booktype实体类和查询结果的对应关系 -->
<id property="id" column="id"/>
<result property="typeName" column="type_name"/>
</association>
</resultMap>
<select id="selectAllBooksAndType" resultMap="BookAndTypeMap">
SELECT
book_info.book_id,
book_info.book_code,
book_info.book_name,
book_info.book_type,
book_info.book_author,
book_info.publish_press,
book_info.publish_date,
book_info.is_borrow,
book_type.id,
book_type.type_name
FROM
book_info
LEFT JOIN
book_type
ON
book_info.book_type = book_type.id
ORDER BY
book_info.book_id ASC
</select>
resultMap总结
resultMap中特殊节点说明:
association:主要是用来为实体类中对象属性映射值的
collection: 用来对主实体类中的集合对象属性映射值
6 嵌套查询
嵌套select查询,也叫N+1查询,也有人称为select指向查询,也就是需要通过多个sql语句完成多表数据查询。代码实现效果跟上面利用连接查询实现效果一致。
1对1实体关系案例:查询所有的图书信息及图书分类信息
分析:
-
建立实体类关系
-
public class BookInfo{ //。。。。bookInfo表对应的属性名 //添加图书分类的属性 private BookType type; }
-
BookInfoDao.java添加一个查询方法
-
/** * 演示:嵌套查询如何配置 * @return */ List<BookInfo> selectAllBooks();
-
BookInfoDao.xml配置sql语句
-
主查表的查询结果
<select id="selectAllBooks" resultMap="BookInfoAndBookType"> select <include refid="commonSql"/> from book_info </select>
-
根据主查表查询的结果,将某一个列值带入到第二个sql查询其他图书
-
<!-- 这个方法不是程序员自己调用,配合第一个sql完成图书分类对象的封装 --> <select id="selectBookTypeByTypeId" resultType="bookType"> select id,type_name from book_type where id=#{typeId} </select>
-
1对多关系案例:查询图书分类及分类下属所有的图书信息
分析:图书分类和图书信息之间1对多关系
- 实体类设计
public class BookType{
//Book_type表对应属性
//图书信息列表:一个分类可以对应多个图书
private List<BookInfo> books;
}
- BookTypeDao.java配置方法
List<BookType> selectAllType();
-
BookTypDao.xml配置sql
<resultMap id="BookTypeAndBooks" type="bookType" autoMapping="true"> <!-- 主键配置 --> <id column="id" property="id"/> <!-- 其他列酌情:如果列名和属性一样,基于automapping完成,否则result明确指定 -->
<collection property="books" ofType="com.woniu.entity.BookInfo" select="com.woniu.dao.BookInfoDao.selectBooksByTypeId" column="id"/> </resultMap>
<select id="selectAllBookType" resultMap="BookTypeAndBooks"> select id,type_name from book_type </select>
-
为第一个sql查询图书信息配置第二个sql的代码实现
-
<select id="selectBooksByTypeId" resultType="bookInfo"> select <include refid="commonSql"/> from book_info where book_type=#{typeId} </select>