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

  1. 数据库创建连接,释放频繁造成系统资源的浪费而影响系统性能。(数据库连接池中的Druid,C3P0已解决)
  2. sql语句在代码中硬编码,造成代码维护不易,实际应用sql变化的可能较大,sql变动要改变代码。
  3. 查询操作时,需要手动将结果集中的数据手动封装到实体中。插入操作时,需要手动将实体的数据设置到sql语句的占位符位置。

MyBatis给出的解决方案:

  • 使用数据库连接池初始化连接资源
  • 将sql语句抽取到xml配置文件中
  • 使用反射,内省等底层技术,自动将实体与表进行属性与字段的字段映射。

3.MyBatis的快速入门(传统方式)

3.1 开发步骤:
  1. 在pom.xml里添加MyBatis的坐标,有两个,一个mybatis,一个mysql
  2. 创建数据表
  3. 编写表对应的实体类
  4. 编写映射文件Mapper.xml
  5. 编写配置文件Config.xml
  6. 测试
3.2 代码实现
  1. 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>
  1. 创建表

  1. 创建实体类
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;
}
  1. 编写映射文件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>
  1. 编写配置文件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&amp;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>
  1. 测试
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映射文件引进来。

加载方式有如下几种:

  1. 使用相对类路径的资源引用。例如<mapper resource="xx/xx/xx.xml">(常用)
  2. 使用完全限定资源定位符(url):例如:<mapper url="file://xxx/xx/xx.xml">
  3. 使用映射接口实现类的完全限定类名:例如:<maper class="xx.xx.xx">
  4. 将包内的映射器接口完全实现全部注册为映射器,例如:<package name="xx.xx">(比第三个范围大)
  • Properties标签

在实际开发中习惯将数据源的配置信息写入到properties文件中,在将它加载到配置文件中


  • TypeAliase标签

这个标签加在configuration标签里,放在数据源环境environments的前边。

引入这个标签后可以简化resultTypeparameterType的编写。除了这些实体类类型,当使用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的映射关系不能满足要求时,我们也可以自定义这其中的映射关系。

    步骤

    1. 定义转换类并继承BaseTypeHandler<>
    2. 覆盖四个未实现的方法,其中setNonNullParameter为java程序设置数据到数据库的回调方法,getNullableResult为查询时mysql的字符串类型转换成java的Type类型的方法。
    3. 在mybatis核心配置文件中进行注册
    4. 测试
7.2 plugins标签
  • mybatis可以使用第三方插件来对功能进行扩展。

  • 分页助手PageHelper是将分页的复杂操作进行封装,使用简单的方式即可获得分页的相关数据。

    分页助手的使用步骤:

    1. 在pom.xml导入PageHelper的坐标
    2. 在mybatis核心配置文件中配置PageHelper插件
    3. 测试分页数据获取

导入坐标(这里要注意版本问题,如果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需要开发者自己完成。
  1. 新建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>
  1. 在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&amp;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类名),即生成表对应的类
  1. 创建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

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值