目录
概述
半自动的ORM持久层框架
具有较高的SQL灵活性,支持高级映射,动态SQL,延时加载和缓存等特性数据库无关性较低
环境的配置
在回顾JDBC时,我们已经创建有Maven工程,而且在pom.xml中也已经导入 mysql 依赖包,这里就直接在原有工程上搭建MyBatis环境,以及使用MyBatis来实现JDBC查询user的操作流程。
MyBatis操作步骤总结
通过上述操作,我们可以总结出 MyBatis 的操作步骤如下:
-
创建 UserMapper.java 映射器接口
-
创建 UserMapper.xml 映射文件
-
在 mybatis-config.xml 环境配置文件中添加 UserMapper.xml 映射文件路径
-
在 MyBatisDemo中编写MyBatis测试代码
- 加载 mybatis-config.xml MyBatis环境配置文件
- 创建 SqlSessionFactory 工厂对象
- 通过 SqlSessionFactory 工厂创建 SqlSession 对象
- 通过 SqlSession 创建 UserMapper接口对象
- 调用 UserMapper 接口方法执行查询操作
- 调用SqlSession.commit()提交事务(查询不需要)
- 关闭 SqlSession 会话
MyBatis环境搭建
首先,在 Maven 项目的 pom.xml 中添加 MyBatis 的依赖 jar 包
<!--导入 mybatis 依赖包-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.2.8</version>
</dependency>
然后,在Maven工程的 resources 目录下创建 MyBatis 环境配置文件 mybatis-config.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<settings>
<!--是否开启自动加载驼峰命名规则映射映射文件 -->
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
<!-- 数据库配置 -->
<environments default="development">
<environment id="development">
<!-- 事务管理器,JDBC类型的事务管理器 -->
<transactionManager type="JDBC" />
<!-- 数据源,池类型的数据源 -->
<dataSource type="POOLED">
<!--设置数据库驱动 -->
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<!--设置数据库url地址-->
<property name="url" value="jdbc:mysql://localhost:3306/mybatis? useUnicode=true&characterEncoding=UTF8&useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=GMT%2B8&allowMultiQueries=true"/>
<!--设置数据库用户名-->
<property name="username" value="root"/>
<!--设置数据库密码-->
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
<!-- 配置映射文件路径 -->
<mappers>
<!--配置xml映射文件路径-->
<mapper resource="mappers/UserMapper.xml" />
<mapper resource="mappers/OrderMapper.xml" />
<mapper resource="mappers/GameMapper.xml" />
<!--配置注解接口路径-->
<!-- <mapper class="mapper.UserMapper" />-->
<!-- <mapper class="mapper.GameMapper" />-->
</mappers>
</configuration>
最后,在Maven工程的main/java下创建MyBatisDemo.java文件,用于测试MyBatis环境是否搭建OK
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
/**
* @desc MyBatis测试
* @date 2020/6/18 上午11:34
*/
public class MyBatisDemo {
public static void main(String[] args) throws Exception {
// 指定mybatis环境配置文件
String resource = "mybatis-config.xml";
// 读取配置文件
InputStream inputStream = Resources.getResourceAsStream(resource);
// 构建sqlSessionFactory
SqlSessionFactory sqlSessionFactory
= new SqlSessionFactoryBuilder().build(inputStream);
// 获取sqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
System.out.println("MyBatis 环境搭建 OK ");
}
注意:如果运行出现异常错误,那么可能是 jar 包导入有问题,或者 mybatis-config.xml 编写有语法错误,相信认真核查一下就可以解决。
MyBatis查询操作
首先,在 Maven 工程的main/java/mapper下创建 UserMapper.java 文件,该文件是user的映射器接口,如下:
package mapper;
import entity.UserEntity;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Result;
import org.apache.ibatis.annotations.Results;
import org.apache.ibatis.annotations.Select;
import java.util.List;
/**
* @desc User映射器接口
* @date 2020/6/19 上午8:59
*/
public interface UserMapper {
/**
* 根据年龄查询用户信息
* @param age 年龄
* @return user 用户实体集合
*/
public List<UserEntity> selectUserByAge(int age);
}
接着,在 Maven 工程的 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表示命名空间,填写之前创建的 UserMapp.java 接口包路径 -->
<mapper namespace="mapper.UserMapper">
<!--结果集映射-->
<resultMap id="userResultMap" type="entity.UserEntity">
<!-- propery表示UserEntity属性名 column表示tb_user表的字段名-->
<id property="id" column="id" />
<result property="userName" column="userName" />
<result property="password" column="password" />
<result property="name" column="name" />
<result property="age" column="age" />
<result property="sex" column="sex" />
<result property="birthday" column="birthday" />
<result property="created" column="created" />
<result property="updated" column="updated" />
</resultMap>
<!--select查询语句-->
<select id="selectUserByAge" resultMap="userResultMap">
select * from tb_user where age > #{age}
</select>
</mapper>
在IDEA中安装一个插件MyBatisX
首先出现红鸟图标即在xml文件中其次在接口实现时会出现一个蓝鸟的图标(点击图标会跳转到映射结果集)
然后,在 Maven 工程的 resources/mybatis-config.xml 配置文件中添加 UserMapper.xml 映射文件的路径,如下:
<configuration>
<!-- 数据库配置 -->
<environments default="development">
...
</environments>
<!--配置映射文件路径-->
<mappers>
<mapper resource="mappers/UserMapper.xml" />
</mappers>
</configuration>
最后,在 Maven 工程的 main/java/MyBatisDemo.java 文件中添加代码执行查询操作,如下:
import entity.UserEntity;
import mapper.UserMapper;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.apache.log4j.Logger;
import org.apache.log4j.Priority;
import java.io.InputStream;
import java.util.List;
/**
* @desc MyBatis测试
* @date 2020/6/18 上午11:34
*/
public class MyBatisDemo {
public static void main(String[] args) throws Exception {
// 指定mybatis环境配置文件
String resource = "mybatis-config.xml";
// 读取配置文件
InputStream inputStream = Resources.getResourceAsStream(resource);
// 构建sqlSessionFactory工厂对象实例
SqlSessionFactory sqlSessionFactory
= new SqlSessionFactoryBuilder().build(inputStream);
// 获取sqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();//工厂模式的工厂方法
// 执行查询操作语句
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);//通过反射获取接口实现对象
List<UserEntity> userEntities = userMapper.selectUserByAge(20);
Logger logger = Logger.getRootLogger();
// logger.log(Priority.DEBUG,"这是一个debug日志");
logger.debug("这是一个debug日志");
logger.info("这是一个info日志");
logger.warn("这是一个warn日志");
logger.error("这是一个error日志");
//关闭sqlSession
sqlSession.close();
System.out.println("MyBatis 环境搭建 OK ");
}
}
CURD操作
create,read,update,delete操作
- 添加CURD方法
- 添加CURD接口方法对应的UserMapper.xml映射
- 使用JUnit单元测试框架测试CURD接口方法
参数传递方式
-
Mapper映射器接口方法参数只有一个且为基本类型
接口方法:
public List<UserEntity> selectUserByAge(int age);
映射结果:
<select id="selectUserByAge" resultMap="userResultMap"> select * from tb_user where age > #{age}; </select>
其中 #{参数名} 表示参数占位符,等价于SQL语句的 ?号,这里的 #{age} 对应的就是接口方法 selectUserByAge 的参数。由于只有一个参数,而且是基本类型,所以写成 #{userAge} 或者 #{ageUser} 都无所谓,反正作用是一样的
-
Mapper映射器接口方法参数只有一个且为引用类型
接口方法:
public int insertUser(UserEntity user);
映射结果:
<insert id="insertUser"> insert into tb_user (id,userName, password, name, age, sex, birthday, created, updated) values (null,#{userName},#{password},#{name},#{age},#{sex},#{birthday},now(),now()); </insert>
接口方法 insertUser 的参数是引用类型,其实传递给 SQL 语句的参数是引用类型的属性值,SQL 语句本身是不支持引用类型的。那引用类型有很多属性(或成员变量),是如何与 SQL 语句的参数一一对应的呢?
答案是使用 #{引用类型的属性名} ,这里需要注意的是属性名不能写错了,否则就无法与 SQL 语句的参数对应,无法正确传递参数哈。
public class UserEntity { private int id; private String userName; private String password; private String name; private int age; private int sex; private Date birthday; private String created; private String updated; }
由于是自增主键,所以不需要传递引用类型的 id 参数,使用 null 代替,数据库会自动生成主键 id 标识
-
Mapper映射器接口方法参数有两个基本类型
接口方法:
public int updateUser(int id, String name);
映射结果:
<update id="updateUser"> update tb_user set name=#{name} where id=#{id}; </update>
接口方法 updateUser 有两个参数且都是基本类型,按理说直接使用 #{参数名} 就可以了,不过一运行居然报错,如下:
### SQL: update tb_user set name=? where id=?; ### Cause: org.apache.ibatis.binding.BindingException: Parameter 'name' not found. Available parameters are [0, 1, param1, param2]
从错误信息描述看,说是参数 name 没有发现,有效的参数是 [0, 1, param1, param2]。意思就是说当遇到不只一个参数时,比如两个参数,就不能用#{参数名}作为占位符,可以用MyBatis提供了两种方式之一。*
-
方式一:#{0} 表示第一个参数 name,#{1} 表示第二个参数 id,#{2} 表示第三个参数...使用如下:
<update id="updateUser"> update tb_user set name=#{0} where id=#{1}; </update>
-
方式二:#{param1} 表示第一个参数 name,#{param2} 表示第二个参数 id,#{param3} 表示第三个参数...使用如下:
<update id="updateUser"> update tb_user set name=#{param1} where id=#{param2}; </update>
-
方式三:给接口方法的参数取别名,只要参数别名和 #{参数名} 相同就可以了。使用如下:
// @Param("id")表示给参数 int id 取别名为id,@Param("name") 表示给参数 name 取别名为name public int updateUser(@Param("id") int id,@Param("name") String name);
<update id="updateUser"> update tb_user set name=#{name} where id=#{id}; </update>
以上三种 MyBatis 参数的传递方式,哪种项目开发中比较常用呢?答案是第三种方式。理由是这种方式的代码可读性更好。想一想上面举例中,是#{name},#{id}作为参数占位符意思让人一目了然,还是#{0},#{1},#{param1},#{param2}呢?答案应该不言而喻。
-
-
Mapper映射器接口方法参数有两个引用类型
接口方法:
public List<UserEntity> selectUserByAgeAndSex(@Param("userOne") UserEntity userOne,@Param("userTwo") UserEntity userTwo);
映射结果1:
<select id="selectUserByAgeAndSex" resultMap="userResultMap"> select * from tb_user where age > #{userOne.age} and sex = #{userTwo.sex}; </select>
映射结果2:
<select id="selectUserByAgeAndSex" resultMap="userResultMap"> select * from tb_user where age > #{param1.age} and sex = #{param2.sex}; </select>
以上两种映射方式都可以,但是如果没有为两个参数取 @Param("userOne") 和 @Param("userTwo") 别名的话,那么就只有映射结果2可以了,映射结果1将会报错。
注: #{0}、#{1}这种参数占位符的方式只适用于参数是基本类型,不适用于参数是引用类型。
-
Mapper映射器接口方法参数有多个(包括基本类型和引用类型)
接口方法:
public List<UserEntity> selectUserByNameAndAge(@Param("name") String name, @Param("user") UserEntity user);
映射结果1:
<select id="selectUserByNameAndAge" resultMap="userResultMap"> select * from tb_user where name = #{name} and age > #{user.age}; </select>
映射结果2:
<select id="selectUserByNameAndAge" resultMap="userResultMap"> select * from tb_user where name = #{param1} and age > #{param2.age}; </select>
模糊查询的方式(四种)---推荐使用第三种
<select id="selectByName" resultType="EmployeeEntity">
select * from tb_employee where name like #{name}
</select>
SqlSession sqlSession = MyBatisUtil.getSession();
IEmpolyeeDao empolyeeDao = sqlSession.getMapper(IEmpolyeeDao.class);
EmployeeEntity = IEmpolyeeDao.selectByName("%"+"张"+"%");
MyBatisUtil.closeSesion(sqlSession);
这种方式可以实现模糊查询,但是有一点不方便的地方就是在调用接口 selectByName() 方法传参时需要手动的添加 "%" 号通配符,有些麻烦。
-
方式一:手动添加"%"通配符
-
xml配置:
-
<select id="fuzzyQuery" resultType="com.bin.pojo.Book"> select * from mybatis.book where bookName like #{info}; </select>
-
@Test public void fuzzyQuery(){ SqlSession sqlSession = MybatisUtils.getSqlSession(); BookMapper mapper = sqlSession.getMapper(BookMapper.class); List<Book> books = mapper.fuzzyQuery("%萨%"); for (Book book : books) { System.out.println(book); } sqlSession.close(); }
-
注意:需要手动添加"%"通配符,显然这种方式很麻烦,并且如果忘记添加通配符的话就会变成普通的查询语句,匹配全部字符查询。
-
缺点:麻烦 易出错
-
方式二:在xml配置文件中添加"%"通配符,拼接字符串形式
-
<select id="fuzzyQuery" resultType="com.bin.pojo.Book"> select * from mybatis.book where bookName like '%${info}%'; </select>
-
注意:在mapper.xml配置文件中添加"%"通配符,但是需要用单引号将其包裹住,但是用单引号裹住之后#{}就无法被识别,要改成${}这种拼接字符串的形式。虽然通过方式二优化了方式一的缺点,但同时也造成了SQL安全性的问题,也就是用户可以进行SQL注入。
-
缺点:不安全,可进行SQL注入
-
方式三:在xml配置文件中添加"%"通配符,借助concat函数(一个mysql函数)
-
<select id="fuzzyQuery" resultType="com.bin.pojo.Book"> select * from mybatis.book where bookName like concat('%',#{info},'%'); </select>
-
注意:解决了SQL注入且能在配置文件中写"%"通配符的问题,完美实现了模糊查询
-
优点:安全方便
-
方式四:与方式三一样使用mysql函数,但使的用是${}形式,不过需要用单引号包裹住
-
<select id="fuzzyQuery" resultType="com.bin.pojo.Book"> select * from mybatis.book where bookName like concat('%','${info}','%'); </select>
-
缺点:****$有sql注入风险
-
总结:#{}是预编译处理,mybatis在处理#{}时,会将其替换成"?",再调用PreparedStatement的set方法来赋值。
${}是拼接字符串,将接收到的参数的内容不加任何修饰的拼接在SQL语句中,会引发SQL注入问题。
ResultMap标记
(MyBatis中最重要最强大也是最复杂的标记)
<!-- resultMap 结果集映射-->
<resultMap id="userResultMap" type="entity.UserEntity" autoMapping="true">
<!-- propery表示UserEntity属性名,column表示tb_user表字段名-->
<id property="id" column="id" />
<result property="userName" column="user_name" />
<result property="password" column="password" />
<result property="name" column="name" />
<result property="age" column="age" />
<result property="sex" column="sex" />
<result property="birthday" column="birthday" />
<result property="created" column="created" />
<result property="updated" column="updated" />
</resultMap>
<!-- 使用 resultMap 标记的结果集映射-->
<select id="selectUserByAge" parameterType="int" resultMap="userResultMap">
select * from tb_user where age > #{age}
</select>
- resultMap 标签:定义 结果集 ORM 映射规则
- id 属性:当前名称空间下的唯一标识(resultMap 属性值填写 id 属性引用 resultMap 定义的结果集映射)
- type 属性:Java 实体类全名
- autoMapping 属性:自动映射开关
- id 子标签:数据库主键字段与Java 实体类主键标识属性映射
- result 子标签:数据库普通字段与 Java 实体类普通属性映射
- property 属性:Java 实体类属性名
- column 属性:数据库字段名
这些标签和属性的作用一目了然,不用做过多解释,但 autoMapping 属性的作用大家可能不太明白。
如果 autoMapping 属性设置为 true,表示开启自动映射开关,意思就是如果数据库字段名和 Java 实体类属性名相同,那么就可以不用使用 id 子标签或 result 子标签手动映射了。
MyBatis 会自动完成映射这些相同的数据库字段名。至于,不相同的数据库字段名,那还是麻烦你手动映射一下哈。
还记得之前讲过的 resultType 属性,它的作用也是完成自动映射,不过它要求数据库表所有字段名和 Java 实体类属性名必须全部相同,否则有与 Java 实体类属性名不同的字段名无法正常映射的。
所以,resultMap 标记 和 autoMapping 属性一起使用,相当于让 resultMap 的一个一个数据库字段纯手工映射变为半自动映射啦,一方面不失灵活性,一方面不失简洁性。
开启 autoMapping 属性,以上 resultMap 标记可以改写如下,是不是简洁多了。
<!-- resultMap 结果集映射-->
<resultMap id="userResultMap" type="entity.UserEntity" autoMapping="true">
<result property="userName" column="user_name" />
</resultMap>
实体类属性名与数据库字段名不一致
-
Java 实体类属性名与数据库表字段名不一致的问题
-
完成复杂(或高级)SQL 操作,比如联表查询(一对一、一对多或多对多)、子查询、分组查询等当实体类属性名和数据库表字段名不同时,有两种情况如下:
-
情况一:如果数据库字段是经典数据库命名规则,Java 实体类属性名是驼峰命名规则,如下:
数据库字段:user_name
Java 属性名:userName
这种情况,有两种解决办法:
一种是使用 resultMap 标记,手动设置数据库字段和实体类属性名的映射关系,如下:
<result property="userName" column="user_name" />
一种是使用 resultType 属性,自动进行结果集映射,不过需要 settings 设置打开自动驼峰命名规则映射开关,如下:
<!-- 开启自动驼峰命名规则映射开关 -->
<setting name="mapUnderscoreToCamelCase" value="true"/>
- 情况二:如果数据库字段和 Java 实体类属性名完全不一致,如下:
数据库字段:my_user_name
Java 属性名:userName
这种情况,只有一种解决办法,那就是 resultMap 标记,如下:
<result property="userName" column="my_user_name" />
<resultMap id="orderResultMap" type="entity.OrderEntity">
<id property="id" column="id"/>
<result property="userId" column="user_id"/>
<result property="createTime" column="create_time"/>
<result property="updateTime" column="update_time"/>
</resultMap>
<!--第一种方式resultMap-->
<select id="getOrderById" resultMap="orderResultMap">
SELECT * FROM tb_order WHERE id = #{id}
</select>
<!--二三种方式都是resultType-->
<select id="getOrderById" resultType="entity.OrderEntity">
<!--第二种取别名-->
SELECT id,user_id as userId,name,create_time as createTime,update_time as updateTime FROM tb_order WHERE id = #{id}
<!--第三种方式打开驼峰命名法开关-->
select * from tb_order where id = #{id}
</select>
在mybatis-config.xml文件中
<settings>
<!--是否开启自动加载驼峰命名规则映射映射文件 -->
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
多表查询(高级查询)
环境搭建
- 数据库的表之间必须有一定的关系并且必须为多表
- 在IDEA中建立对应的实体类(如何在实体类中体现表之间的关系呢?) ---- 在一个实体类中引用另一个实体类的属性
- 建立映射接口
- 映射结果集,对应的映射文件即xml
- 全局配置(mybatis-config.xml中)配置对应的映射文件
一 对 一
查询条件
public interface GameMapper {
/**
* 根据角色ID查询账号信息
* @param id 角色Id
* @return 角色实体对象
*/
public RoleEntity selectRoleById(int id);
/**
* 根据游戏名查询游戏账号
* @param name 游戏名
* @return 游戏实体类
*/
public GameEntity selectGameByName(String name);
/**
* 根据玩家名查询游戏
* @param name 玩家名
* @return 玩家实体类
*/
public PlayerEntity selectPlayerByName(String name);
}
关联查询(两种)
-
第一种
<result property="account.id" column="aid" /> <result property="account.username" column="username" /> <result property="account.password" column="password" />
-
现在我们暂时先抛开 MyBatis 框架,直接从数据库出发写一写关联查询的 SQL 语句,如下:
select r.*,a.* from tb_role as r join tb_account as a on r.account_id=a.id where r.id=1
另外,还有一种不使用 join 关键字的 SQL 语句写法,如下:
select r.*,a.* from tb_role as r,tb_account as a where r.account_id=a.id and r.id=1
第一种写法,使用 join 关键字,本质上是采用的内连接(inner join)。
第二种写法,不使用 join 关键字,本质上是采用交叉连接(cross join),也即生成两表的笛卡尔积,得到的记录相当于两表记录的乘积。
以上两种写法查询结果是相同的,但推荐使用第一种,因为第一种性能比第二种高,特别是在有大表的情况下。
理由是第二种交叉连接将产生更多的记录,然后通过 where 后的 r.account_id=a.id 条件过滤不需要的记录,而第一种内连接会直接过滤不需要的记录,所以执行效率更高。
我们回到 MyBatis 框架,看一下在 XML 映射文件中,如何实现关联查询映射。
首先,我们需要建立 RoleEntity 实体类与 AccountEntity 实体类之间的关联关系,如下:
public class RoleEntity {
private int id;
private String profession;
private int rank;
private int money;
private AccountEntity account; //关联引用属性
...
}
这样,通过账号名查询的账号信息就可以映射到 account 属性中。注意 account 属性的 get 和 set 方法要记得添加上去,还有 toString 方法要重写一下,添加 account 属性的打印信息。
现在,我们来编写映射文件(GameMapper.xml)中 SQL 语句映射,如下:
<?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="mapper.GameMapper">
<resultMap id="roleResultMap" type="entity.RoleEntity">
<id property="id" column="id" />
<result property="profession" column="profession" />
<result property="rank" column="rank" />
<result property="money" column="money" />
<result property="account.id" column="aid" />
<result property="account.username" column="username" />
<result property="account.password" column="password" />
</resultMap>
<select id="selectRoleById" resultMap="roleResultMap">
select r.*,a.id as aid,a.user_name,a.password from tb_role as r join tb_account as a on r.account_id=a.id where r.id=#{id}
</select>
</mapper>
这个地方使用到了级联赋值,多级之间用.
进行引用,此处我们只有一级,可以有很多级
-
注意事项:
select r.*,a.*from tb_role as r join tb_account as a on r.account_id=a.id where r.id=#{id}
写法存在的问题:这会存在两个相同字段名 id,结果集映射时 accout 的 id 值会被 role 的 id 值所覆盖
-
第二种
<!-- association:用于映射关联查询单个对象信息 property:将关联查询的账号信息映射到属性 account 上 --> <association property="account" javaType="entity.AccountEntity"> <id property="id" column="aid" /> <result property="userName" column="user_name" /> <result property="password" column="password" /> </association>
和之前单表映射相比,没什么太大差别,就是多了 association 子标记以及相应的内容罢了。
意思很也简单,就是将关联查询结果中的账号信息,具体而言就是 a.id,a.user_name,a.password ,映射到 AccountEntity 实例中,也就是 RoleEntity.account 属性。
现在,我们来编写映射文件中 SQL 语句映射,如下:
<?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="mapper.GameMapper"> <resultMap id="roleResultMap" type="entity.RoleEntity"> <id property="id" column="id" /> <result property="profession" column="profession" /> <result property="rank" column="rank" /> <result property="money" column="money" /> <!-- association:用于映射关联查询单个对象信息 property:将关联查询的账号信息映射到属性 account 上 --> <association property="account" javaType="entity.AccountEntity"> <id property="id" column="aid" /> <result property="userName" column="user_name" /> <result property="password" column="password" /> </association> </resultMap> <select id="selectRoleById" resultMap="roleResultMap"> select r.*,a.id as aid,a.user_name,a.password from tb_role as r join tb_account as a on r.account_id=a.id where r.id=#{id} </select> </mapper>
-
注:****autoMapping 属性:自动映射开关,关联查询默认 false,当设置为 true 则当字段名和实体类属性名相同时可以自动映射
-
在mybatis-config.xml文件中打开
<!--数据库配置--> <!--设置数据库url地址--> <property name="url" value="jdbc:mysql://localhost:3306/sgs?useUnicode=true&characterEncoding=UTF-8&useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=GMT%2B8&allowMultiQueries=true"/>
最后,我们在 MyBatisTest 中添加一个单元测试方法,如下:
@Test public void selectRoleByAccountNameTest() { RoleEntity roleEntity = gameMapper.selectRoleById(1); System.out.println(roleEntity); Assert.assertNotNull(roleEntity); }
执行测试,结果如下:
2020-07-15 10:49:28,860 [main] [mapper.GameMapper.selectRoleById]-[DEBUG] ==> Preparing: select r.*,a.id,a.user_name,a.password from tb_role as r join tb_account as a on r.account_id=a.id where r.id=? 2020-07-15 10:49:28,954 [main] [mapper.GameMapper.selectRoleById]-[DEBUG] ==> Parameters: 1(Integer) 2020-07-15 10:49:28,985 [main] [mapper.GameMapper.selectRoleById]-[DEBUG] <== Total: 1 RoleEntity{id=1, profession='战士', rank=10, money=2000, account=AccountEntity{id=1, userName='潇洒哥', password='12345'}}
这样,我们通过游戏角色ID查询到了游戏账号信息,并封装在 RoleEntity 对象的 account 属性中
-
子查询(两种)
-
第一种
<!-- association:用于映射关联查询单个对象信息 property:将关联查询的账号信息映射到属性 account 上 column:传递子查询参数 select:子查询id --> <association property="account" javaType="entity.AccountEntity" column="account_id" select="selectAccountById"> </association>
老规矩,现在我们暂时先抛开 MyBatis 框架,直接从数据库出发写一写子查询的 SQL 语句,如下:
select * from tb_account where id=(select account_id from tb_role where id=1)
这条子查询 SQL 语句由两条 select 语句组成,不用过多解释了,应该很容易理解。
现在,我们回到 MyBatis 框架,之前关联查询时我们已经建立 RoleEntity 实体类与 AccountEntity 实体类之间的关联关系,所以,这里我们直接来编写映射文件中 SQL 语句映射,如下:
<?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="mapper.GameMapper"> <resultMap id="roleResultMap" type="entity.RoleEntity"> <id property="id" column="id" /> <result property="profession" column="profession" /> <result property="rank" column="rank" /> <result property="money" column="money" /> <!-- association:用于映射关联查询单个对象信息 property:将关联查询的账号信息映射到属性 account 上 column:传递子查询参数 select:子查询id --> <association property="account" javaType="entity.AccountEntity" column="account_id" select="selectAccountById"> </association> </resultMap> <select id="selectRoleById" resultMap="roleResultMap"> select * from tb_role where id=#{id} </select> <select id="selectAccountById" resultType="entity.AccountEntity"> select * from tb_account where id=#{accountId} </select> </mapper>
以上可以看到,子查询和关联查询不一样的地方在于 association 标记多了 column 和 select 这两个属性。
- column 的值是 account_id,这是 tb_role 表的外键 ID 字段名,意思是将该字段名对应的字段值作为参数传递给指定的子查询
- select 的值是 selectAccountById,这是子查询 select 语句的ID,表示指定 ID 定义的子查询,也就是 select * from tb_account where id=#{accountId} 这条查询语句。这里的参数 #{accountId} 对应的就是column 的值
以上子查询的参数只有一个,那么如果子查询的参数不止一个,又该怎么办呢?比如是复合主键。如果要处理复合主键,可以使用column= "{prop1=col1,prop2=col2}" ,prop表示类属性名,col表示表字段名。
执行测试,结果如下:
2020-07-15 10:57:56,314 [main] [mapper.GameMapper.selectRoleById]-[DEBUG] ==> Preparing: select * from tb_role where id=? 2020-07-15 10:57:56,360 [main] [mapper.GameMapper.selectRoleById]-[DEBUG] ==> Parameters: 1(Integer) 2020-07-15 10:57:56,392 [main] [mapper.GameMapper.selectAccountById]-[DEBUG] ====> Preparing: select * from tb_account where id=? 2020-07-15 10:57:56,392 [main] [mapper.GameMapper.selectAccountById]-[DEBUG] ====> Parameters: 1(Integer) 2020-07-15 10:57:56,392 [main] [mapper.GameMapper.selectAccountById]-[DEBUG] <==== Total: 1 2020-07-15 10:57:56,392 [main] [mapper.GameMapper.selectRoleById]-[DEBUG] <== Total: 1 RoleEntity{id=1, profession='战士', rank=10, money=2000, account=AccountEntity{id=1, userName='null', password='12345'}}
结果和关联查询一样,而且从打印调试信息可以看到,子查询是先后执行了两条简单的 select 语句。
这里还有一个小问题,就是账号信息的 userName 属性值是 null。为何会如此呀,原因是 tb_account 字段名是user_name,AccountEntity 实体类属性名是 userName,无法使用 resultType 自动映射。
解决办法有两个:
-
子查询结果集映射不使用 resultType 自动映射,改为 resultMap 手动映射,如下:
<resultMap id="accountResultMap" type="entity.AccountEntity"> <id property="id" column="id" /> <result property="userName" column="user_name" /> <result property="password" column="password" /> </resultMap> <select id="selectAccountById" resultMap="accountResultMap"> select * from tb_account where id=#{accountId} </select>
-
在全局配置文件 mybatis-config.xml 开启 settings 开关
<configuration> <settings> <!-- 开启自动驼峰命名规则映射开关 --> <setting name="mapUnderscoreToCamelCase" value="true"/> </settings> ... </configuration>
结果如下:
RoleEntity{id=1, profession='战士', rank=10, money=2000, account=AccountEntity{id=1, userName='潇洒哥', password='12345'}}
userName 已经有值了,说明问题得以解决
-
第二种
子查询方式一种给第二个查询传递了一个参数,如果需要给第二个查询传递多个参数怎么办呢?
<association property="属性" select="查询对应的select的id"
column="{key1=父查询字段1,key2=父查询字段2,key3=父查询字段3}" />
这种相当于给子查询传递了一个 map,子查询中 需要用过 map 的 key 获取对应的条件
<resultMap id="roleResultMap" type="entity.RoleEntity">
<id property="id" column="id" />
<result property="profession" column="profession" />
<result property="rank" column="rank" />
<result property="money" column="money" />
<!-- association:用于映射关联查询单个对象信息
property:将关联查询的账号信息映射到属性 account 上
column:传递子查询参数
select:子查询id
-->
<association property="account" javaType="entity.AccountEntity"
column="{aid=account_id,rid=id}" select="selectAccountById">
</association>
</resultMap>
一 对 多
查询条件
根据游戏名称,查询游戏账号信息
我们在之前创建的映射器接口 GameMapper.java 中添加接口方法,如下:
/**
* 根据游戏名查询游戏账号
* @param name 游戏名
* @return 游戏实体类
*/
public GameEntity selectGameByName(String name);
接下来,我分别演示关联查询和子查询方式实现接口方法的映射。
关联查询方式
现在我们暂时先抛开 MyBatis 框架,直接从数据库出发写一写关联查询的 SQL 语句,如下:
select g.*,a.* from tb_game as g join tb_account as a on g.id=a.game_id where g.name ='英雄联盟'
现在,我们回到 MyBatis 框架,看一下在 XML 映射文件中,如何实现关联查询映射。
首先,我们需要建立 GameEntity 实体类与 AccountEntity 实体类之间的关联关系,如下:
public class GameEntity {
private int id;
private String name;
private String type;
private String operator;
private List<AccountEntity> accounts; //关联引用游戏账号集合
}
这样,通过游戏名查询的账号信息就可以映射到 accounts 属性中。注意 accounts 属性的 get 和 set 方法要记得添加上去,还有 toString 方法要重写一下,添加 accounts 属性的打印信息。
你可能有疑惑,这里为何要使用 List 集合呢?原因是一款游戏对应的账号信息可能有多个,而 MyBatis 可以通过使用 resultMap 的 collection 标记将关联查询的多条记录映射到一个 List 集合属性中。
现在,我们来编写映射文件中 SQL 语句映射,如下:
<?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="mapper.GameMapper">
<resultMap id="gameResultMap" type="entity.GameEntity">
<id property="id" column="id" />
<result property="name" column="name" />
<result property="type" column="type" />
<result property="operator" column="operator" />
<!-- collection:一对多映射,关联当前分类下产品信息
property:映射集合结果
ofType:结果集类型 -->
<collection property="accounts" ofType="entity.AccountEntity">
<id property="id" column="a.id" />
<result property="userName" column="user_name" />
<result property="password" column="password" />
</collection>
</resultMap>
<select id="selectGameByName" resultMap="gameResultMap">
select g.*,a.id as aid,a.user_name,a.password from tb_game as g join tb_account as a on g.id=a.game_id where g.name =#{name}
</select>
</mapper>
以上和一对一关联查询相比,association 标记改为 collection 标记,表示是一对多映射;javaType 属性改为ofType 属性,表示集合里的元素类型。除此以外,和一对一关联查询差不多。
最后,我们在 MyBatisTest 中添加一个单元测试方法,如下:
@Test
public void selectGameByNameTest() {
GameEntity gameEntity = gameMapper.selectGameByName("英雄联盟");
System.out.println(gameEntity);
Assert.assertNotNull(gameEntity);
}
执行单元测试方法,结果如下:
2020-07-15 18:34:05,260 [main] [mapper.GameMapper.selectGameByName]-[DEBUG] ==> Preparing: select g.*,a.* from tb_game as g join tb_account as a on g.id=a.game_id where g.name =?
2020-07-15 18:34:05,307 [main] [mapper.GameMapper.selectGameByName]-[DEBUG] ==> Parameters: 英雄联盟(String)
2020-07-15 18:34:05,354 [main] [mapper.GameMapper.selectGameByName]-[DEBUG] <== Total: 2
GameEntity{id=1, name='英雄联盟', type='MOBA', operator='腾讯游戏', accounts=[AccountEntity{id=1, userName='潇洒哥', password='12345'}, AccountEntity{id=4, userName='进击巨人', password='11111'}]}
子查询方式
首先,我们暂时先抛开 MyBatis 框架,直接从数据库出发写一写子查询的 SQL 语句,如下:
select * from tb_account where game_id = (select id from tb_game where name ='英雄联盟')
接着,这里我们直接来编写映射文件中 SQL 语句映射,如下:
<?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="mapper.GameMapper">
<resultMap id="gameResultMap" type="entity.GameEntity">
<id property="id" column="id" />
<result property="name" column="name" />
<result property="type" column="type" />
<result property="operator" column="operator" />
<!-- collection:用于映射关联查询多个对象信息
property:将关联查询的账号信息映射到属性 accounts 上
column:传递子查询参数
select:子查询id
-->
<collection property="accounts" ofType="entity.AccountEntity"
column="id" select="selectAccountById" />
</resultMap>
<select id="selectGameByName" resultMap="gameResultMap">
select * from tb_game where name =#{name}
</select>
<select id="selectAccountById" resultType="entity.AccountEntity">
select * from tb_account where game_id=#{gameId}
</select>
</mapper>
执行测试,结果如下:
2020-07-15 21:25:11,086 [main] [mapper.GameMapper.selectGameByName]-[DEBUG] ==> Preparing: select * from tb_game where name =?
2020-07-15 21:25:11,164 [main] [mapper.GameMapper.selectGameByName]-[DEBUG] ==> Parameters: 英雄联盟(String)
2020-07-15 21:25:11,211 [main] [mapper.GameMapper.selectAccountById]-[DEBUG] ====> Preparing: select * from tb_account where game_id=?
2020-07-15 21:25:11,211 [main] [mapper.GameMapper.selectAccountById]-[DEBUG] ====> Parameters: 1(Integer)
2020-07-15 21:25:11,211 [main] [mapper.GameMapper.selectAccountById]-[DEBUG] <==== Total: 2
2020-07-15 21:25:11,211 [main] [mapper.GameMapper.selectGameByName]-[DEBUG] <== Total: 1
GameEntity{id=1, name='英雄联盟', type='MOBA', operator='腾讯游戏', accounts=[AccountEntity{id=1, userName='潇洒哥', password='12345'}, AccountEntity{id=4, userName='进击巨人', password='11111'}]}
多 对 多
查询条件:根据玩家名,查询游戏信息
我们在之前创建的映射器接口 GameMapper.java 中添加接口方法,如下:
/**
* 根据玩家名查询游戏
* @param name 玩家名
* @return 玩家实体类
*/
public PlayerEntity selectPlayerByName(String name);
接下来,我分别演示关联查询和子查询方式实现接口方法的映射。
关联查询方式:
现在我们暂时先抛开 MyBatis 框架,直接从数据库出发写一写关联查询的 SQL 语句,如下:
select p.*,g.* from tb_player as p join tb_player_game as p_g on p.id = p_g.player_id join tb_game as g on g.id = p_g.game_id where p.name = '张三'
这条 SQL 语句有点长,涉及三表关联查询,因为多对多相比一对多而言,多了一张中间表哈。
我们在数据库中执行这条 SQL 语句,结果如下
现在,我们回到 MyBatis 框架,看一下在 XML 映射文件中,如何实现关联查询映射。
首先,我们需要建立 PlayerEntity 实体类 与 GameEntity 实体类之间的关联关系,如下:
public class PlayerEntity {
private int id;
private String name;
private int age;
private int sex;
private List<GameEntity> games;
}
这样,通过玩家名查询的游戏信息就可以映射到 games 属性中。对了,set 和 get 方法以及 toString 方法自己补上哈。
现在,我们来编写映射文件中 SQL 语句映射,如下:
<?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="mapper.GameMapper">
<resultMap id="playerResultMap" type="entity.PlayerEntity">
<id property="id" column="id" />
<result property="name" column="name" />
<result property="age" column="age" />
<result property="sex" column="sex" />
<collection property="games" ofType="entity.GameEntity">
<id property="id" column="id" />
<result property="name" column="name" />
<result property="type" column="type" />
<result property="operator" column="operator" />
</collection>
</resultMap>
<select id="selectPlayerByName" resultMap="playerResultMap" >
select p.*,g.* from tb_player as p
join tb_player_game as p_g on p.id = p_g.player_id
join tb_game as g on g.id = p_g.game_id where p.name = #{name}
</select>
</mapper>
不过多说,和一对多关联映射差不多。
最后,我们在 MyBatisTest 中添加一个单元测试方法,如下:
@Test
public void selectPlayerByNameTest() {
PlayerEntity playerEntity = gameMapper.selectPlayerByName("张三");
System.out.println(playerEntity);
Assert.assertNotNull(playerEntity);
}
执行测试,结果如下:
2020-07-16 21:57:18,545 [main] [mapper.GameMapper.selectPlayerByName]-[DEBUG] ==> Preparing: select p.*,g.* from tb_player as p join tb_player_game as p_g on p.id = p_g.player_id join tb_game as g on g.id = p_g.game_id where p.name = ?
2020-07-16 21:57:18,623 [main] [mapper.GameMapper.selectPlayerByName]-[DEBUG] ==> Parameters: 张三(String)
2020-07-16 21:57:18,669 [main] [mapper.GameMapper.selectPlayerByName]-[DEBUG] <== Total: 2
PlayerEntity{id=1, name='张三', age=23, sex=1, games=[GameEntity{id=1, name='英雄联盟', type='MOBA', operator='腾讯游戏', accounts=null}, GameEntity{id=2, name='绝地求生', type='TPS', operator='蓝洞游戏', accounts=null}]}
可以思考一下,如果这里需要打印账号信息,你可以尝试一下能否自己独立搞定。
子查询方式:
我们暂时先抛开 MyBatis 框架,直接从数据库出发写一写子查询的 SQL 语句,如下:
select * from tb_game where id in(
select game_id from tb_player_game where player_id = (
select id from tb_player where name ='张三'
)
)
这条 SQL 语句涉及三张表,两个子查询,由于子查询返回多个 id,所以用 in
接着,这里我们直接来编写映射文件中 SQL 语句映射,如下:
<resultMap id="playerResultMap" type="entity.PlayerEntity">
<id property="id" column="id" />
<result property="name" column="name" />
<result property="age" column="age" />
<result property="sex" column="sex" />
<collection property="games" ofType="entity.GameEntity" column="id" select="selectGameById" />
</resultMap>
<select id="selectPlayerByName" resultMap="playerResultMap" >
select * from tb_player where name = #{name}
</select>
<select id="selectGameById" resultType="entity.GameEntity">
select * from tb_game where id in (select game_id from tb_player_game where player_id=#{id})
</select>
执行测试,结果如下:
2020-07-16 22:59:17,601 [main] [mapper.GameMapper.selectPlayerByName]-[DEBUG] ==> Preparing: select * from tb_player where name = ?
2020-07-16 22:59:17,647 [main] [mapper.GameMapper.selectPlayerByName]-[DEBUG] ==> Parameters: 张三(String)
2020-07-16 22:59:17,679 [main] [mapper.GameMapper.selectGameById]-[DEBUG] ====> Preparing: select * from tb_game where id in (select game_id from tb_player_game where player_id=?)
2020-07-16 22:59:17,679 [main] [mapper.GameMapper.selectGameById]-[DEBUG] ====> Parameters: 1(Integer)
2020-07-16 22:59:17,679 [main] [mapper.GameMapper.selectGameById]-[DEBUG] <==== Total: 2
2020-07-16 22:59:17,679 [main] [mapper.GameMapper.selectPlayerByName]-[DEBUG] <== Total: 1
PlayerEntity{id=1, name='张三', age=23, sex=1, games=[GameEntity{id=1, name='英雄联盟', type='MOBA', operator='腾讯游戏', accounts=null}, GameEntity{id=2, name='绝地求生', type='TPS', operator='蓝洞游戏', accounts=null}]}
标签:语句,name,映射,where,查询,sql,MyBatis,tb,id
From: https://www.cnblogs.com/yangcurry/p/18474927