MyBatis小结
0. MyBatis的官方文档(强力推荐)
https://mybatis.org/mybatis-3/zh/index.html
1.什么是MyBatis
- MyBatis是一个基于java的持久层框架,内部封装了jdbc,使开发者只需要关注sql语句本身,不需要花费精力去处理加载驱动,创建连接,创建Statement等繁杂过程。
- MyBatis采用了ORM思想解决了实体与数据库的映射问题。
- ORM:又称为ORMapping,Object Relationship Mapping 对象关系映射。对象指面向对象。关系指关系型数据库。也就是java到mysql的映射,开发者可以以面向对象的思想来管理数据库。
2. 为什么要使用MyBatis
对比之前学习的jdbc,我们发现原始jdbc操作存在的问题:
jdbc回顾:
https://blog.csdn.net/weixin_44226752/article/details/113881324?spm=1001.2014.3001.5501
- 数据库创建连接,释放频繁造成系统资源的浪费而影响系统性能。(数据库连接池中的Druid,C3P0已解决)
- sql语句在代码中硬编码,造成代码维护不易,实际应用sql变化的可能较大,sql变动要改变代码。
- 查询操作时,需要手动将结果集中的数据手动封装到实体中。插入操作时,需要手动将实体的数据设置到sql语句的占位符位置。
MyBatis给出的解决方案:
- 使用数据库连接池初始化连接资源
- 将sql语句抽取到xml配置文件中
- 使用反射,内省等底层技术,自动将实体与表进行属性与字段的字段映射。
3.MyBatis的快速入门(传统方式)
3.1 开发步骤:
- 在pom.xml里添加MyBatis的坐标,有两个,一个mybatis,一个mysql
- 创建数据表
- 编写表对应的实体类
- 编写映射文件Mapper.xml
- 编写配置文件Config.xml
- 测试
3.2 代码实现
- pom.xml导入mybatis坐标
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.5</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
</dependency>
<dependency>
//导入lombok是为了简化实体类的代码,并不是必要的
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.18</version>
<!-- <scope>provided</scope> -->
</dependency>
- 创建表
- 创建实体类
package com.southwind.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data//生成Getter,Setter
@AllArgsConstructor//生成全参构造
@NoArgsConstructor//生成无参构造
public class Account {
private long id;
private String username;
private String password;
private int age;
}
- 编写映射文件AccountMapper.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="com.southwind.mapper.AccountMapper">
<insert id="save" parameterType="com.southwind.entity.Account">
insert into user(username,password,age) values(#{username},#{password},#{age})
</insert>
</mapper>
- 编写配置文件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>//配置
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"></transactionManager>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=UTF-8"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>
<!-- 注册配置文件 -->
<mappers>
<mapper resource="com/southwind/mapper/AccountMapper.xml"></mapper>
<mapper resource="com/southwind/repository/AccountRepository.xml"></mapper>
</mappers>
</configuration>
- 测试
public class test {
public static void main(String[] args) {
InputStream inputStream = test.class.getClassLoader().getResourceAsStream("config.xml");
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
Account account = new Account(4,"lisi","527814",53);
sqlSession.insert("com.southwind.mapper.AccountMapper.save",account);
sqlSession.commit();
sqlSession.close();
}
}
4.对MyBatis中一些代码的说明
4.1 Mapper.xml映射文件
-
namespace:通常设置为对应的Mapper.java接口的全类名。
-
select标签表示执行查询操作,还有insert,update,delete可选择。用#{实体属性名}方式引用实体中的属性值。
-
id:调用MyBatis方法时需要用到的参数。
-
namespace+id:作为statement参数传入sqlSession方法中
-
resultType:表示sql语句的返回值类型
-
parameterType:表示传入sql语句中参数的类型。
-
关于一些其他的配置查看这个链接文档:https://mybatis.org/mybatis-3/zh/sqlmap-xml.html
4.2 Config.xml配置文件
- environments
这行代码中的default与environment中id对应,当有多个数据源环境时,可以通过调用不同id来替换数据源。
- transactionManager
事务管理器(transactionManager)类型有两种。JDBC,MANAGED。JDBC:是直接使用了jdbc的提交和回滚,依赖于从数据源得到的连接来管理事务作用域。MANAGED是让容器来管理事务的整个生命周期。
- dataSource
数据源(dataSource)类型有三种。UNPOOLED,POOLED,JNDI。UNPOOLED:这个数据源的实现只是每次被请求时打开和关闭连接。POOLED:这种数据源的实现利用“池”的概念将jdbc连接对象组织起来。JNDI:这个数据源的实现是为了能在如EJB 或应用服务器这类容器中使用,容器可以集中或在外部配置数据源,然后放置一个 JNDI 上下文的引用。
- mapper标签:
该标签的作用是加载映射的,将上面的Mapper.xml映射文件引进来。
加载方式有如下几种:
- 使用相对类路径的资源引用。例如
<mapper resource="xx/xx/xx.xml">
(常用) - 使用完全限定资源定位符(url):例如:
<mapper url="file://xxx/xx/xx.xml">
- 使用映射接口实现类的完全限定类名:例如:
<maper class="xx.xx.xx">
- 将包内的映射器接口完全实现全部注册为映射器,例如:
<package name="xx.xx">
(比第三个范围大)
- Properties标签:
在实际开发中习惯将数据源的配置信息写入到properties文件中,在将它加载到配置文件中
- TypeAliase标签
这个标签加在configuration标签里,放在数据源环境environments的前边。
引入这个标签后可以简化resultType和parameterType的编写。除了这些实体类类型,当使用java.Long.Integer这种包装类型时可以不用这个标签来简化,直接使用int来代替就行,mybatis框架帮我们设置好了一些常用的类型的别名。
4.3 sqlsession会话对象的创建
public static void main(String[] args) {
//将配置文件加载进inputStream
//test指的是当前的类名
InputStream inputStream = test.class.getClassLoader().getResourceAsStream("config.xml");
//创建SqlSession工厂构建器SqlSessionFactoryBuilder
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
//通过SqlSessionFactoryBuilder创建工厂对象SqlSessionFactory,需要myBatis配置文件流作为参数。
SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(inputStream);
//通过SqlSessionFactory的openSession()方法来创建sqlSession实例。
SqlSession sqlSession = sqlSessionFactory.openSession();
String statement = "com.southwind.mapper.AccountMapper.save";
Account account = new Account(3534,"coder","527814",53);
sqlSession.insert(statement,account);
sqlSession.commit();
sqlSession.close();
}
- SqlSessionFactory有多个方法创建sqlSession实例。常用的有两个
方法 | 解释 |
---|---|
openSession() | 会默认开启一个事务,,但事务不会自动提交,也就意味着需要手动提交该事务,更新操作数据才会持久化到数据库中 |
openSession(boolean autoCommit) | 参数为是否自动提交,如果设置为true,那么不需要手动提交事务。 |
- sqlSession会话对象有许多方法供调用。常见的如 int insert(String statement, Object parameter) , int update(String statement, Object parameter)等。statement就是命名空间.id值,parameter参数类型可以是int等基本类型,也可以是实体类,和映射文件的parameterType类型相对应。
- 在执行完增删改操作后要commit,查询操作不用。或者openSession(true).
- 使用完sqlSession后要关闭资源。
5. MyBatis的代理开发方式(主流)
5.1 传统方式与代理开发方式的比较
- 在上文的mybatis快速入门中,由于只写了一个Insert方法,所以为了简便没有使用Dao接口来定义方法,在开发中我们都会使用Dao接口来定义方法,实现类来描述具体的方法。下面我们使用Dao方式来写一个mybatis开发代码,然后分析它的问题,进而引出代理开发,从而得到代理开发的优势。
传统Dao方式
创建接口:
public interface UserDao(){
List<User> findAll() throws IOException;
}
创建实现类
public class UserDaoImpl implements UserDao {
@override
public List<User> findAll() throws IOException {
InputStream inputStream = test.class.getClassLoader().getResourceAsStream("config.xml");
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
List<User> userList = sqlSession.selectList("userMapper.findAll");//userMapper.findAll就是命名空间namespace+标识id
sqlSession.close();
return userList;
}
}
测试
@Test
public void testTraditionDao() throws IOException {
UserDao userDao = new UserDaoImpl();
List<User> all = userDao.findAll();
System.out.println(all);
}
上面我们通过Dao传统方式实现了一个查询所有用户的操作。但存在一个问题,就是Dao的实现类好像并没有干什么实质性的工作,只是调用SqlSession的相应api来定位映射文件Mappper.xml中的sql语句,真正对DB进行操作的工作其实是由框架通过mapper中的SQL完成的。所以,MyBatis框架就抛开了Dao的实现类,直接定位到映射文件mapper中的相应SQL语句,对DB进行操作。这种对Dao的实现方式称为Mapper的动态代理方式。Mapper动态代理方式无需程序员实现Dao接口。接口是由MyBatis结合映射文件自动生成的动态代理实现的
参考:https://blog.csdn.net/qq_44715697/article/details/109711008
5.2 动态代理开发方式:(重要,主流)
- 采用MyBatis的代理开发方式实现Dao层的开发,是现在的主流方式。Mapper接口开发方式只需要程序员编写Mapper接口,(相当于Dao接口),由Mybatis框架根据接口定义创建Mapper的动态代理对象,代理对象的方法同上边的Dao接口实现类方法。
需要遵循的规范
- Mapper.xml文件中的namespace与Mapper接口的全限定名相同。
- Mapper接口方法名和Mapper.xml中定义的每个statement的id相同。
- Mapper接口方法的输入参数类型和Mapper.xml中定义的每个sql的paramenterType的类型相同
- Mapper接口方法的输出参数类型和Mapper.xml中定义的每个sql的resultType的类型相同。
测试代码
实体类省略
定义一个AccountRepository接口
package com.southwind.repository;
import com.southwind.entity.Account;
import java.util.List;
public interface AccountRepository {
public int save(Account account);
public int update(Account account);
}
创建接口对应的AccountRepository.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="com.southwind.repository.AccountRepository">
<insert id="save" parameterType="com.southwind.entity.Account">
insert into user(username,password,age) values (#{username},#{password},#{age})
</insert>
</mapper>
在config.xml中注册AccountRepository.xml及配置数据源
<mappers>
<mapper resource="com/southwind/repository/AccountRepository.xml"></mapper>
</mappers>
生成sqlSession对象并调用接口的代理对象完成相关的业务操作
public static void main(String[] args) {
InputStream inputStream = test2.class.getClassLoader().getResourceAsStream("config.xml");
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(inputStream);
SqlSession sqlSession=sqlSessionFactory.openSession();
// 获取实现接口的代理对象
AccountRepository accountRepository = sqlSession.getMapper(AccountRepository.class);
// 添加对象
Account account = new Account(12,"coderchen", "ywedx", 22);
int result = accountRepository.save(account);
System.out.println(result+"添加成功");
// 要提交才能运行成功,close()方法可以节约资源
sqlSession.commit();
sqlSession.close();
动态代理开发方式中我们只是定义了接口,并没有去实现接口。通过遵循mybatis的规则来完成实现,从而调用方法。而在传统开发中,我们需要定义接口,实现接口,再调用方法。相比发现少了一步接口的实现,其他没变。
6. 动态SQL
- 在mybatis的映射文件中,前面我们的sql都是比较简单的,但有时出现业务逻辑比较复杂时,简单sql语句并不能满足要求,这时就需要动态sql。
- 动态sql可以简化代码的开发,减少开发的工作量,程序可以自动根据业务参数来决定sql的组成。
普通的sql查询语句:
<select id="findByAccount" parameterType="com.southwind.entity.Account" resultType="com.southwind.entity.Account">
select * from account where id=#{id} and username=#{username} and password=#{password} and age=#{age}
</select>
存在的问题:查询时只能将account实体类的几个属性都传入才能查出来,任何除了主键外任何一项为空都查不出来结果。因为数据表中并不存在username=xxx,password=xxx,age=null的这一项。因此sql语句的限制性很大。如果我们想要查询出age=xx的所有人,这时候就要使用动态sql。
动态sql查询:
<select id="findByAccount" parameterType="com.southwind.entity.Account" resultType="com.southwind.entity.Account">
select * from account
<where>
<if test="id!=0">
id=#{id}
</if>
<if test="username!=null">
and username = #{username}
</if>
<if test="password!=null">
and password = #{password}
</if>
<if test="age!=0">
and age = #{age}
</if>
</where>
</select>
这样的话当要查询age=xx的人时,只要传入age参数就行,实际上执行的sql语句为 select * from account where age = xx。where 标签可以自动判断是否要删除语句块中的and关键字,如果检测到where直接跟and进行拼接,则自动删除and,通常情况下where和if标签结合使用。
使用的标签:if和 where
上面的动态sql也可以用choose标签和when标签来实现:
<select id="findByAccount" parameterType="com.southwind.entity.Account"
resultType="com.southwind.entity.Account">
select * from t_account
<where>
<choose>
<when test="id!=0">
id = #{id}
</when>
<when test="username!=null">
username = #{username}
</when>
<when test="password!=null">
password = #{password}
</when>
<when test="age!=0">
age = #{age}
</when>
</choose>
</where>
</select>
trim标签:
trim 标签中的 prefix 和 suffix 属性会被⽤于⽣成实际的 SQL 语句,会和标签内部的语句进⾏拼接,如 果语句前后出现了 prefixOverrides 或者 suffixOverrides 属性中指定的值,MyBatis 框架会⾃动将其删 除。
set标签:
set标签用于update操作,会根据参数自动生成sql语句。
<update id="update" parameterType="com.southwind.entity.Account">
update t_account
<set>
<if test="username!=null">
username = #{username},
</if>
<if test="password!=null">
password = #{password},
</if>
<if test="age!=0">
age = #{age}
</if>
</set>
where id = #{id}
</update>
foreach标签:
foreach 标签可以迭代⽣成⼀系列值,这个标签主要⽤于 SQL 的 in 语句。
总结一下出现的标签:if, wherre ,choose, when ,trim ,set ,foreach
7.Config.xml配置文件深入
7.1 typeHandlers标签
- java和mysql中的数据类型并不一样,因此无论是mybatis在预处理语句中设置一个参数还是从结果集中获取一个值时,都要用类型处理器来将mysql和java类型进行一一映射,mybatis提供了一些默认的映射,一般会自动执行映射关系。
-
当java和mysql的映射关系不能满足要求时,我们也可以自定义这其中的映射关系。
步骤:
- 定义转换类并继承BaseTypeHandler<>
- 覆盖四个未实现的方法,其中setNonNullParameter为java程序设置数据到数据库的回调方法,getNullableResult为查询时mysql的字符串类型转换成java的Type类型的方法。
- 在mybatis核心配置文件中进行注册
- 测试
7.2 plugins标签
-
mybatis可以使用第三方插件来对功能进行扩展。
-
分页助手PageHelper是将分页的复杂操作进行封装,使用简单的方式即可获得分页的相关数据。
分页助手的使用步骤:
- 在pom.xml导入PageHelper的坐标
- 在mybatis核心配置文件中配置PageHelper插件
- 测试分页数据获取
导入坐标(这里要注意版本问题,如果pagehelper使用5以上的版本,配置文件可能会有点变动)
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>3.7.5</version>
</dependency>
<dependency>
<groupId>com.github.jsqlparser</groupId>
<artifactId>jsqlparser</artifactId>
<version>0.9.1</version>
</dependency>
核心配置文件中配置PageHelper插件。
//注意:分页助手的插件配置在mapper的前边,最好在environment上面
<plugins>
<plugin interceptor="com.github.pagehelper.PageHelper">
//指定方言,5以上版本有所改变
<property name="dialect" value="mysql"/>
</plugin>
</plugins>
测试分页数据:
public static void main(String[] args) {
InputStream inputStream = test2.class.getClassLoader().getResourceAsStream("config.xml");
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(inputStream);
SqlSession sqlSession=sqlSessionFactory.openSession();
// 获取实现接口的代理对象
AccountRepository accountRepository = sqlSession.getMapper(AccountRepository.class);
PageHelper.startPage(2, 2);
List<Account> list=accountRepository.findAll();
for (Account account : list) {
System.out.println(account);
}
PageInfo<Account> pageInfo = new PageInfo<>(list);
System.out.println("当前页数:"+pageInfo.getPageNum());
System.out.println("数据总条数:" + pageInfo.getTotal());
System.out.println("总页数:" + pageInfo.getPages());
System.out.println("每页的长度:"+pageInfo.getPageSize());
// 要提交才能运行成功,close()方法可以节约资源
sqlSession.commit();
sqlSession.close();
}
}
7.3 总结一下Config.xml核心配置文件中的标签
- environments:数据源环境配置标签
- mappper:加载映射,将Mapper.xml映射文件引入
- properties:该标签可以加载外部的properties文件
- typeAliases标签:设置类型别名,简化resultType和parameterType的书写。
- typehandlers:对java和mysql的数据类型进行自定义映射时使用的标签
- plugins:配置mybatis插件
8. 多表操作
- 在开发中经常会用到多表操作。多表操作有三种,一对一,一对多,多对多。
- 一对一查询:主表的主键和从表的外键建立关系
- 一对多查询:在从表(多表)的一方建立字段,字段作为外键指向主键
- 多对多查询:需要创建第三张表,中间表至少有两个字段,这两个字段分别作为外键指向各自一方的主键。
8.1 一对一查询
用户表和订单表的关系为,一个用户有多个订单,一个订单只从属于一个用户。
一对一查询的需求:查询一个订单,与此同时查询出该订单所属的用户。
一对一查询语句:select * from orders o,user u where o.uid=u.id;
查询的结果:
创建Order 和User实体
public class Order{
private int id;
private Date ordertime;
private double total;
//代表当前订单从属于哪一个用户
private User user;
}
public class User{
private int id;
private string username;
private String password;
private Date birthday;
}
创建OrderMapper接口
public interface OrderMapper{
List<Order> findAll();
}
配置OrderMapper.xml
<mapper namespace="com.itheima.mapper.OrderMapper">
<resultMap id="orderMap" type="com.itheima.domain.Order">
<result column="uid" property="user.id"></result>
<result column="username" property="user.username"></result>
<result column="password" property="user.password"></result>
<result column="birthday" property="user.birthday"></result>
</resultMap>
<select id="findAll" resultMap="orderMap">
select * from orders o,user u where o.uid=u.id
</select>
</mapper>
其中resultMap还可以写为
<resultMap id="orderMap" type="com.itheima.domain.Order">
<result property="id" column="id"></result>
<result property="ordertime" column="ordertime"></result>
<result property="total" column="total"></result>
<association property="user" javaType="com.itheima.domain.User">
<result column="uid" property="id"></result>
<result column="username" property="username"></result>
<result column="password" property="password"></result>
<result column="birthday" property="birthday"></result>
</association>
</resultMap>
测试结果:
//这里的OrderMapper指的是OrderMapper接口类
OrderMapper mapper = sqlSession.getMapper(OrderMapper.class);
List<Order> all = mapper.findAll();
for(Order order : all){
System.out.println(order);
}
8.2 一对多查询
定义两张表:学生表和老师表
一个老师对应多个学生,在查询老师的时候将对应的学生也查询出来,即一对多查询。
student类
@Data
public class Student {
private int id;
private String name;
private int tid;
}
teacher类
@Data
public class Teacher {
private int id;
private String name;
// 一个老师多个学生
private List<Student> students;
}
创建TeacherMapper接口
//获取指定老师及老师下面的学生
public interface TeacherMapper {
public Teacher getTeacher(int id);
}
配置TeacherMapper.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="com.southwind.mapper.TeacherMapper">
<!--当老师id确定时,在老师和学生表中查询学生的id 名字 和老师的名字,老师的id-->
<select id="getTeacher" resultMap="TeacherStudent">
select s.id sid,s.name sname,t.name tname,t.id tid
from student s,teacher t
where s.tid=t.id and t.id=#{id}
</select>
<!-- 对查询出来的结果做结果集映射,集合的话使用collection
JavaType和ofType都是用来指定对象类型的
JavaType是用来指定pojo中属性的类型
ofType指定的是映射到list集合属性中pojo的类型-->
<!-- id 在这个xml里的唯一标识 type 对应映射的实体类-->
<resultMap id="TeacherStudent" type="com.southwind.entity.Teacher">
<result property="name" column="tname"/>
<collection property="students" ofType="com.southwind.entity.Student">
<!-- 配置数据库表和实体类的映射关系-->
<result property="id" column="sid"/>
<result property="name" column="sname"/>
<result property="tid" column="tid"/>
</collection>
</resultMap>
</mapper>
在config.xml配置mapper
<mapper resource="com/southwind/mapper/TeacherMapper.xml"></mapper>
测试结果:
package com.southwind.Test;
import com.southwind.entity.Teacher;
import com.southwind.mapper.TeacherMapper;
import com.southwind.repository.AccountRepository;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.InputStream;
/**
* Created with Intellij IDEA
* Description:
* user: CoderChen
* Date: 2021-04-29
* Time: 21:20
*/
public class testOneToThree {
public static void main(String[] args) {
InputStream inputStream = testOneToThree.class.getClassLoader().getResourceAsStream("config.xml");
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
// 获取实现接口的代理对象
TeacherMapper teacherMapper = sqlSession.getMapper(TeacherMapper.class);
Teacher teacher1 = teacherMapper.getTeacher(1);
Teacher teacher2 = teacherMapper.getTeacher(2);
System.out.println(teacher1.getName());
System.out.println(teacher1.getStudents());
System.out.println(teacher2.getName());
System.out.println(teacher2.getStudents());
}
}
8.3 多对多查询
用户表和角色表的关系为,一个用户有多个角色,一个角色被多个用户使用
多对多查询的需求:查询用户同时查询出该用户的所有角
对应的sql语句:select u.,r.,r.id rid from user u left join user_role ur on u.id=ur.user_id inner join role r on ur.role_id=r.id;
查询的结果:
创建Role实体,修改User实体
public class User {
private int id;
private String username;
private String password;
private Date birthday;
//代表当前用户具备哪些订单
private List<Order> orderList;
//代表当前用户具备哪些角色
private List<Role> roleList;
}
public class Role {
private int id;
private String rolename;
}
添加UserMapper接口方法
List<User> findAllUserAndRole();
配置UserMapper.xml
<resultMap id="userRoleMap" type="com.itheima.domain.User">
<result column="id" property="id"></result>
<result column="username" property="username"></result>
<result column="password" property="password"></result>
<result column="birthday" property="birthday"></result>
<collection property="roleList" ofType="com.itheima.domain.Role">
<result column="rid" property="id"></result>
<result column="rolename" property="rolename"></result>
</collection>
</resultMap>
<select id="findAllUserAndRole" resultMap="userRoleMap">
select u.*,r.*,r.id rid from user u left join user_role ur on u.id=ur.user_id
inner join role r on ur.role_id=r.id
</select>
测试结果:
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> all = mapper.findAllUserAndRole();
for(User user : all){
System.out.println(user.getUsername());
List<Role> roleList = user.getRoleList();
for(Role role : roleList){
System.out.println(role);
}
System.out.println("----------------------------------");
}
8.4 小结
一对一配置:使用做配置
一对多配置:使用+做配置
多对多配置:使用+做配置
8.5 注意
- Type,javaType ,ofType的类型都要是全类名,或者在config,xml配置一下typeAliases标签,避免找不到该类。
- javaType对应的是单个pojo,ofType对应的是多个pojo,在list集合中。
9.注解开发
9.1 mybatis的常用注解
@Insert:实现新增
@Update:实现更新
@Delete:实现删除
@Select:实现查询
@Result:实现结果集封装
@Results:可以与@Result 一起使用,封装多个结果集
@One:实现一对一结果集封装
@Many:实现一对多结果集封装
9.2 MyBatis的增删改查
private UserMapper userMapper;
@Before
public void before() throws IOException {
InputStream resourceAsStream = Resources.getResourceAsStream("SqlMapConfig.xml");
SqlSessionFactory sqlSessionFactory = new
SqlSessionFactoryBuilder().build(resourceAsStream);
SqlSession sqlSession = sqlSessionFactory.openSession(true);
userMapper = sqlSession.getMapper(UserMapper.class);
}
@Test
public void testAdd() {
User user = new User();
user.setUsername("测试数据");
user.setPassword("123");
user.setBirthday(new Date());
userMapper.add(user);
}
@Test
public void testUpdate() throws IOException {
User user = new User();
user.setId(16);
user.setUsername("测试数据修改");
user.setPassword("abc");
user.setBirthday(new Date());
userMapper.update(user);
}
@Test
public void testDelete() throws IOException {
userMapper.delete(16);
}
@Test
public void testFindById() throws IOException {
User user = userMapper.findById(1);
System.out.println(user);
}
@Test
public void testFindAll() throws IOException {
List<User> all = userMapper.findAll();
for(User user : all){
System.out.println(user);
}
}
修改MyBatis的核心配置文件,我们使用了注解替代的映射文件,所以我们只需要加载使用了注解的Mapper接口即可
<mappers>
<!--扫描使用注解的类-->
<mapper class="com.itheima.mapper.UserMapper"></mapper>
</mappers>
或者指定扫描包含映射关系的接口所在的包也可以
<mappers>
<!--扫描使用注解的类所在的包-->
<package name="com.itheima.mapper"></package>
</mappers>
MyBatis的UserMapper接口,(使用注解后不需要接口对应的mapper.xml映射文件,直接在接口上加注解就行)
public interface UserMapper {
@Insert("insert into user values(#{id},#{username},#{password},#{birthday})")
public void save(User user);
@Update("update user set username=#{username},password=#{password} where id=#{id}")
public void update(User user);
@Delete("delete from user where id=#{id}")
public void delete(int id);
@Select("select * from user where id=#{id}")
public User findById(int id);
@Select("select * from user")
public List<User> findAll();
}
9.3 MyBatis的注解进行复杂映射的开发
实现复杂关系映射之前我们可以在映射文件中通过配置来实现,使用注解开发后,我们可以使用@Results注解,@Result注解,@One注解,@Many注解组合完成复杂关系的配置
10. 逆向工程
-
官方文档(英文):http://mybatis.org/generator/quickstart.html
-
MyBatis框架需要:实体类,自定义的Mapper接口,对应的Mapper.xml映射文件,Config.xml配置文件,测试类(包含sqlSession对象)
-
传统的开发中上述的组件都要开发者手动创建,使用逆向工程可以帮我们自动创建实体类,自定义的Mapper接口,对应的Mapper.xml映射文件三个组件,减轻开发者工作量,提高工作效率。
10.1 如何使用
- MyBatis Generator,简称MBG,是一个专门为MyBatis框架开发者定制的代码生成器,可以自动生成MyBatis框架所需的实体类,mapper接口,mapper.xml文件,支持基本的CRUD操作,但是一些相对复杂的sql需要开发者自己完成。
- 新建maven工程,在pom.xml添加MyBatis Generator坐标
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.5</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
</dependency>
<dependency>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-core</artifactId>
<version>1.3.2</version>
</dependency>
- 在resources目录下创建MBG配置文件generatorConfig.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
"http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
<context id="testTable" targetRuntime="MyBatis3">
<!--jdbcConnection 配置数据库连接信息-->
<jdbcConnection
driverClass="com.mysql.jdbc.Driver"
connectionURL="jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=UTF-8"
userId="root"
password="root">
</jdbcConnection>
<!-- javaModelGenerator 配置javabean的生成策略 -->
<javaModelGenerator targetPackage="com.southwind.entity" targetProject=".\src\main\java"></javaModelGenerator>
<!-- sqlMapGenerator 配置SQL映射文件生成策略 -->
<sqlMapGenerator targetPackage="com.southwind.repository" targetProject=".\src\main\java"></sqlMapGenerator>
<!-- javaClientGenerator 配置mapper接口的生成策略 -->
<javaClientGenerator type="XMLMAPPER" targetPackage="com.southwind.repository" targetProject=".\src\main\java"></javaClientGenerator>
<!-- table 配置目标数据库表(tablename:表名 domainObjectName:javaBean类名)-->
<table tableName="user" domainObjectName="User"></table>
</context>
</generatorConfiguration>
- jdbcConnection配置数据库连接信息
- javaModelGenerator配置javaBean的生成策略
- sqlMapGenerator 配置SQL映射文件生成策略
- javaClientGenerator 配置mapper接口的生成策略
- table 配置目标数据库表(tablename:表名 domainObjectName:javaBean类名),即生成表对应的类
- 创建Generator执行类
public class test {
public static void main(String[] args) {
List<String> warings = new ArrayList<>();
boolean overwrite = true;
String genCig = "/generatorConfig.xml";
File configFile = new File(Main.class.getResource(genCig).getFile());
ConfigurationParser configurationParser = new ConfigurationParser(warings);
Configuration configuration=null;
try {
configuration = configurationParser.parseConfiguration(configFile);
} catch (IOException e) {
e.printStackTrace();
} catch (XMLParserException e) {
e.printStackTrace();
}
DefaultShellCallback callback = new DefaultShellCallback(overwrite);
MyBatisGenerator myBatisGenerator = null;
try {
myBatisGenerator = new
MyBatisGenerator(configuration,callback,warings);
} catch (InvalidConfigurationException e) {
e.printStackTrace();
}
try {
myBatisGenerator.generate(null);
System.out.println("生成成功");
} catch (SQLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
执行完上述三步就会在指定的位置生成实体类User,UserExample,接口UserMapper,映射文件UserMapper.xml。MBG根据配置配置文件中的数据库表对应生成实体类,然后生成常用的CRUD接口和sql语句。
生成的只是基本的crud的动态SQL,如果要进行级联操作,即一对多,则要手动实现。
文件目录
10.2 逆向工程的优缺点
- 优点:帮助我们自动生成java代码,大大加快我们的开发效率
- 缺点:生成的文件太过冗余,不必要的代码过多。尤其是sql映射文件,里面的配置内容太多,对应dao层,提供的方法比较有限,需要自动扩展。
10.3 常见问题
Mybatis-generator生成Example使用心得
https://blog.csdn.net/qq_43318965/article/details/106665863
https://blog.csdn.net/liangwanmian/article/details/79121383
11. 延迟加载
12. 缓存
13.Mapper.xml映射文件的补充
- 在Mapper.xml里除了上面介绍的insert,update,delete,select标签外(4.1),还有一些其他的标签,例如cache,cache-ref,sql,resultMap等。
- cache:该命名空间的缓存配置
- cache-ref:引用其他命名空间的缓存配置
- sql:可以被其他语句引用的可重用语句块。
- resultMap:描述如何从数据库加载对象,是最复杂也是最强大的元素。
13.1 sql标签
- 这个标签用来定义可重用的SQL代码片段,以便在其他语句中使用。参数可以在加载的时候确定下来并且可以在不同的include元素中定义不同的参数值。例如:
<sql id="userColumns"> ${alias}.id,${alias}.username,${alias}.password </sql>
这个SQL片段可以在其他语句中使用,例如:
<select id="selectUsers" resultType="map">
select
<include refid="userColumns"><property name="alias" value="t1"/></include>
<include refid="userColumns"><property name="alias" value="t2"/></include>
from some_table t1 cross join some_table t2
</select>
- 就等价于
select t1.id,t1.username,t1.password,t2.id,t2.username,t2.password from some_table t1 cross join some_table t2;
- cross join:交叉连接,即笛卡尔连接,返回的是两个表行的乘积,即两个表分别为n行和m行,则结果为n*m行。
- property标签表示对指定的属性赋值。
- include标签表示引用sql片段。
也可以在include元素的refid属性或者内部语句中使用属性值,例如
<sql id="sometable">
${prefix}Table
</sql>
<sql id="someinclude">
from
<include refid="${include_target}"/>
</sql>
<select id="select" resultType="map">
select
field1, field2, field3
<include refid="someinclude">
<property name="prefix" value="Some"/>
<property name="include_target" value="sometable"/>
</include>
</select>
- 对应的sql语句:
select filed1,filed2,filed3 from Sometable;
13.2 resultMap标签
1. 定义映射规则
先看一个代码块
<select id="selectUsers" resultType="com.someapp.model.User">
select id, username, hashedPassword
from some_table
where id = #{id}
</select>
-
这个代码块即是当id确定时从some_table表中查询id,username,hashedPassword。返回值类型为com.someapp.model.User,即这个表对应的实体类,上面的代码我们也是这样写的,但这其中的隐含条件是mysql表的属性名和pojo实体类的属性名一一对应且相等,这种情况下mybatis会在后台自动创建一个ResultMap,根据属性名来完成表和类的映射。
-
如果表属性和实体类属性不匹配时,有两种方法解决。一是在select 语句中设置列别名来完成匹配。二是使用ResultMap来显式配置。
-
举例:
//首先来完成表和实体类属性之间的字段映射关系 <resultMap id="userResultMap" type="User"> <id property="id" column="user_id" /> <result property="username" column="user_name"/> <result property="password" column="hashed_password"/> </resultMap>
id标签指明主键列,result配置数据库字段和pojo属性的映射规则。
注意:type 中的user即是对应映射的类实体,如果没有设置typeAlias ,则要用全类名,不让mybatis找不到该类。
property:对应的实体类属性
column:对应的数据库字段名
//在select语句中引用上面的resultMap,即resultMap属性值是上面的id <select id="selectUsers" resultMap="userResultMap"> select user_id, user_name, hashed_password from some_table where id = #{id} </select>
上面的代码我们可以总结出ResultMap的一个作用:定义映射规则
2. 关联/级联查询
见多表操作(8)
这里要注意一下:association标签用于一对一查询,collection标签用于一对多查询,多对多查询不常用,可以简化为多个一对多。
通过多表查询中对resultMap的应用,我们发现在多表查询中,resultMap的作用还是定义映射规则,将要查询的表的属性和pojo的属性进行映射,在一对一时使用association标签,然后将另一张表的映射规则写在里面,一对多时使用collection标签,将另一张表的映射规则写在里面,select里的sql语句也就是简单的多表查询。
3.resultMap元素的构成
<resultMap>
<constructor> <!-- 配置构造方法 -->
<idArg/>
<arg/>
</constructor>
<id/> <!--指明哪一列是主键-->
<result/> <!--配置映射规则-->
<association/> <!--一对一-->
<collection/> <!--一对多-->
<discriminator> <!--鉴别器级联-->
<case/>
</discriminator>
</resultMap>
4.总结
参考文章:
https://juejin.cn/post/6844903583633113095
https://juejin.cn/post/6844904166918193160