学习:狂神

MyBatis

搭建环境

  1. 创建user表,添加数据
  2. 创建模块,导入坐标
  3. 编写M小yBatis核心配置文件->替换连接信息解决硬编码问题
  4. 编写SQL映射文件->统一管理sq语句,解决硬编码问题
  5. 编码
    1. 定义entity类
    2. 加载核心配置文件,获取SqlSessionFactory对象
    3. 获取SqlSession对象,执行SQL语句
    4. 释放资源

导jar包

  • 核心包:mybatis
  • 依赖包:
    • commons-logging 日志包
    • log4j 日志包
  • 驱动包:mysql-connector-java

注册实体类

1
2
3
4
5
6
public class User {
private int id;
private String name;
private String pwd;
}
//mybatis通过映射实现,不需要set方法

注册配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?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.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql:///mybatis?serverTimezone=GMT%2B8&amp;useSSL=false"/>
<property name="username" value="root"/>
<property name="password" value="000000"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="UserMapper.xml"/>
</mappers>
</configuration>

注册映射文件

1
2
3
4
5
6
7
8
9
10
<?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="test">
<select id="findAll" resultType="User">
select * from user;
</select>
</mapper>

测试类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Test
public void test1() throws IOException {

InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
//获取session工厂对象
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
// 获取session回话对象
SqlSession sqlSession = sqlSessionFactory.openSession();
//执行操作
List<User> userList = sqlSession.selectList("test.findAll");

System.out.println(userList);
//释放资源
sqlSession.close();
}

增删改查

  • 字符串替换 (类似于泛型属性

    • 替换前
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    @Select("select * from user where id = #{id}")
    User findById(@Param("id") long id);

    @Select("select * from user where name = #{name}")
    User findByName(@Param("name") String name);

    @Select("select * from user where email = #{email}")
    User findByEmail(@Param("email") String email);

    // 其它的 "findByXxx" 方法
    • 优化后(方便简洁但不安全)
    1
    2
    @Select("select * from user where ${column} = #{value}")
    User findByColumn(@Param("column") String column, @Param("value") String value);
  • sql片段

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    <sql id="if-id-name">
    <if test="id!=null">
    id=#{id}
    </if>
    <if test="name!=null">
    and name=#{name}
    </if>
    </sql>

    <select id="findOne" resultMap="UserPwd">
    select * from user
    <where>
    <include refid="if-id-name"></include>
    </where>
    </select>
  • forEach

    1
    select * from `user` where id in (1,2,3);
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    <select id="findForEach" resultMap="UserPwd" parameterType="map">
    select * from user where id in
    <foreach collection="items" item="id" open="(" separator="," close=")">
    #{id}
    </foreach>
    </select>
    // foreach: 遍历集合collection中的内容
    // index : 下标 item:元素
    // open,close : 开始,结束符
    // separator : 自定义分割符( , or and ...)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    @Test
    public void Test2() throws IOException {
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);

    Map map=new HashMap<>();
    ArrayList<Integer> items = new ArrayList<>();
    items.add(1);
    items.add(2);
    items.add(3);
    map.put("items",items);

    List<User> userList = mapper.findForEach(map);
    for (User user : userList) {
    System.out.println(user);
    }

    }

配置解析

MyBatis 的配置文件包含了会深深影响 MyBatis 行为的设置和属性信息。 配置文档的顶层结构如下:

1、属性(properties)

  • 可以直接引入外部文件

  • 可以在其中增加一些属性配置

  • 如果两个文件有同一个字段,优先使用外部配置文件的!

    配置属性文件config.properties

    1
    2
    3
    4
    driver=com.mysql.cj.jdbc.Driver
    url=jdbc:mysql:///mybatis?serverTimezone=GMT%2B8&useSSL=false
    username=root
    password=000000

    引入外部配置文件(如果两个文件有同一个字段,优先使用外部配置文件的!)

    1
    2
    3
    4
    5
       <properties resource="config.properties"/>

    <properties resource="config.properties">
    <property name="password" value="优先读取属性文件"/>
    </properties>
    1
    2
    3
    4
    5
    6
    <dataSource type="POOLED">
    <property name="driver" value="${driver}"/>
    <property name="url" value="${url}"/>
    <property name="username" value="${username}"/>
    <property name="password" value="${password}"/>
    </dataSource>

2、设置(settings)

settings(设置)

  • 日志工厂

    STDOUT_LOGGING
    1
    2
    3
    4
    <settings>
    // mybatis 自带
    <setting name="logImpl" value="STDOUT_LOGGING"/>
    </settings>
    log4j
    1
    2
    3
    <settings>
    <setting name="logImpl" value="log4j"/>
    </settings>
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    public class MybatisTest03 {

    //日志对象,参数为当前类的class (反射)
    static Logger logger = Logger.getLogger(MybatisTest03.class);

    @Test
    //日志级别
    public void log4jTest(){
    logger.info("info->日志");
    logger.debug("debug->日志");
    logger.error("error->日志");
    }
    }

3、类型别名(typeAliases)

  • 根据属性

    1
    2
    3
    <typeAliases>
    <typeAlias type="com.entity.User" alias="User"/>
    </typeAliases>
  • 扫描实体类的包,它的默认别名就为这个类的类名,首字母小写!

    1
    2
    3
    <typeAliases>
    <package name="com.entity"/>
    </typeAliases>
  • 在实体类中使用注解(优先级最高)

    1
    2
    3
    4
    @Alias("Candy")
    public class User {
    ....
    }

4、其他配置

  1. 类型处理器(typeHandlers)
  2. 对象工厂(objectFactory)
  3. 插件(plugins)
  4. 数据库厂商标识

5、环境配置(environments)

环境配置

6、映射器(mappers)

1
2
3
4
<!-- 方式一:使用相对于类路径的资源引用 -->
<mappers>
<mapper resource="org/mybatis/builder/UserMapper.xml"/>
</mappers>
1
2
3
4
<!-- 方式二:使用映射器接口实现类的完全限定类名 -->
<mappers>
<mapper class="org.mybatis.builder.UserMapper"/>
</mappers>
1
2
3
4
<!-- 方式三:将包内的映射器接口实现全部注册为映射器 -->
<mappers>
<package name="org.mybatis.builder"/>
</mappers>

使用方式二、三 时注意:

  • 接口和他的Mapper配置文件必须同名!
  • 接口和他的Mapper配置文件必须在同一个包下!

ResultMap

  • 实体类中属性名与数据库中字段不一致(属性名字段名不一致解决方案)

    1
    2
    3
    private int id;	
    private String name;
    private String password; (数据库中为 pwd )
    • 数据库查询原理(可用起别名解决)
    1
    2
    3
    4
    <select id="findOne" resultType="User">
    <!-- select * from user where id=#{id} -->
    select id,name,pwd as password from user where id=#{id}
    </select>
  • ResultMap

    1
    2
    3
    4
    5
    6
    7
    8
    <!--    结果集映射  column 数据库字段; property 实体类属性-->
    <resultMap id="UserMap" type="user">
    <result column="pwd" property="password"/>
    </resultMap>

    <select id="findOne" resultMap="UserMap">
    select * from user where id=#{id}
    </select>

结果映射(resultMap)

  • constructor —用于在实例化类时,注入结果到构造方法中

    • idArg - ID 参数;标记出作为 ID 的结果可以帮助提高整体性能
    • arg - 将被注入到构造方法的一个普通结果
  • id –一个 ID 结果;标记出作为 ID 的结果可以帮助提高整体性能

  • result – 注入到字段或 JavaBean 属性的普通结果

  • association —— 一个复杂类型的关联;许多结果将包装成这种类型

    • 嵌套结果映射 – 关联可以是 resultMap 元素,或是对其它结果映射的引用
  • collection —— 一个复杂类型的集合

    • 嵌套结果映射 – 集合可以是 resultMap 元素,或是对其它结果映射的引用
  • discriminator ——使用结果值来决定使用哪个 resultMap

    • case —— 基于某些值的结果映射

      • 嵌套结果映射 – case 也是一个结果映射,因此具有相同的结构和元素;或者引用其它的结果映射

分页

Limit 分页

mysql 代码

1
select * from user limit #{startIndex},#{num}

接口层

1
public List<User> findOfLimit(Map<String,Integer> map);

测试

1
2
3
4
Map<String,Integer> map= new HashMap<>();
map.put("startIndex",0);
map.put("num",2);
List<User> userList = mapper.findOfLimit(map);

RowBounds 分页

mysql 代码

1
select * from user;

接口层

1
public List<User> findByRowBounds();

测试层

1
2
3
4
// RowBounds实现类
RowBounds rowBounds = new RowBounds(2,3);
// 基于 java代码层面实现 (limit分页 基于sql层)
List<User> userList = sqlSession.selectList("com.dao.UserMapper.findByRowBounds",null,rowBounds);

插件分页

注解开发

1
2
3
4
5
6
7
8
9
10
@Select("select * from user")
public List<User> findAll();

@Select("select * from user where id=#{uid} and name=#{uname}")
public List<User> findOne(@Param("uid") int id, @Param("uname") String name);

@Insert("insert into user value (#{id},#{name},#{password})")
public int addUser(User user);

@Delete()
@Param()设置传入形参对接sql的限定名

复杂查询

  • 多对一

    Teacher

    1
    2
    private int id;
    private String name;

    Student

    1
    2
    3
    private int id;
    private String name;
    private Teacher teacher;
    association —n. 协会,联盟,社团;联合;联想
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
        <select id="findStu2" resultMap="StuMap">
    select s.id sid,s.name sname,t.name tname // sql字段取别名,防止编译混淆
    from student s ,teacher t
    where s.tid =t.id ;
    </select>

    <resultMap id="StuMap" type="com.entity.Student"> //推荐使用类型别名,避免全限定名
    <result property="id" column="sid"/>
    <result property="name" column="sname"/>
    <association property="teacher" javaType="com.entity.Teacher">
    <result property="name" column="tname"/>
    </association>
    </resultMap>

    <!-- ========================================================== -->

    <select id="findStu" resultMap="StuTeacher">
    select * from student;
    </select>
    <resultMap id="StuTeacher" type="com.entity.Student">
    <result property="id" column="id"/>
    <result property="name" column="name"/>
    <association property="teacher" column="tid" javaType="com.entity.Teacher" select="findTch"/>
    </resultMap>
    <select id="findTch" resultType="com.entity.Teacher"> // 根据tid查询老师
    select * from teacher where id=#{tid}
    </select>
  • 一对多

    Student

    1
    2
    3
    private int id;
    private String name;
    private int tid;

    Teacher

    1
    2
    3
    private int id;
    private String name;
    private List<Student> student;
    collection
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    <select id="findById" parameterType="_int" resultMap="TH">
    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>

    <resultMap id="TH" type="teacher">
    <id property="id" column="tid"/>
    <result property="name" column="tname"/>
    <!-- javaType :指定属性的返回值类型,
    ofType : 集合中的泛型信息通常使用ofType获取-->
    <collection property="student" javaType="ArrayList" ofType="student">
    <result property="id" column="sid"/>
    <result property="name" column="sname"/>
    <result property="tid" column="tid"/>
    </collection>
    </resultMap>

    <!-- =========================================================== -->

    <select id="findById2" resultMap="TH2">
    select * from teacher where id=#{id}
    </select>

    <resultMap id="TH2" type="teacher">
    <collection property="student" javaType="ArrayList" ofType="student" select="findStu" column="id"/>
    </resultMap>

    <select id="findStu" resultType="student">
    select * from student where tid=#{tid}
    </select>

缓存

一级缓存 (本地缓存)是基于 PerpetualCache(MyBatis自带)的 HashMap 本地缓存,作用范围为 session 域内。当 session flush(刷新)或者 close(关闭)之后,该 session 中所有的 cache(缓存)就会被清空。

  • 一级缓存失效的情况:

    1. 查询不同的东西

    2. 增删改操作,可能会改变原来的数据,所以必定会刷新缓存!

    3. 查询不同的Mapper,Xml

    4. 手动清理缓存

      1
      sqlSession.clearCache();
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      @Test
      public void test2(){
      SqlSession sqlSession = sqlSessionFactory.openSession();
      TeacherMapper mapper = sqlSession.getMapper(TeacherMapper.class);
      TeacherMapper mapper1 = sqlSession.getMapper(TeacherMapper.class);

      System.out.println(mapper.findById(1));
      sqlSession.clearCache();
      System.out.println(mapper1.findById(1));

      sqlSession.close();
      }

二级缓存 是全局缓存,作用域超出 session 范围之外,可以被所有 SqlSession 共享

  • 配置

    1
    2
    3
    4
    5
    <cache
    eviction="FIFO"
    flushInterval="60000"
    size="512"
    readOnly="true"/>
  • 测试

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    @Test
    public void test(){
    SqlSession sqlSession1 = sqlSessionFactory.openSession();
    SqlSession sqlSession2 = sqlSessionFactory.openSession();

    TeacherMapper mapper = sqlSession1.getMapper(TeacherMapper.class);
    TeacherMapper mapper1 = sqlSession2.getMapper(TeacherMapper.class);

    List<Teacher> teachers = mapper.findById(1);
    for (Teacher teacher : teachers) {
    System.out.println(teacher);
    }
    sqlSession1.close();
    sqlSession1.clearCache(); //在二级缓存中无效
    logger.info("----------------------");
    List<Teacher> teachers1 = mapper1.findById(1);
    for (Teacher teacher : teachers1) {
    System.out.println(teacher);
    }
    System.out.println(teachers == teachers1);
    sqlSession2.close();
    }
  • 二级缓存遗留问题:为什么要openSession.close();后,才能从二级缓存中查数据,不是先执行二再一么?

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
        @Test
    public void TowCache(){
    SqlSession sqlSession1 = sqlSessionFactory.openSession();
    SqlSession sqlSession2 = sqlSessionFactory.openSession();

    UserMapper mapper1 = sqlSession1.getMapper(UserMapper.class);
    UserMapper mapper2 = sqlSession2.getMapper(UserMapper.class);

    System.out.println(mapper1.findAll());
    sqlSession1.close(); //在此处关闭,查询只执行了1遍
    // sqlSession1.clearCache(); //对二级缓存无效
    System.out.println(mapper2.findAll());
    // sqlSession1.close(); //在此处关闭,查询执行了2遍
    sqlSession2.close();
    }
    //注意:二级缓存需要openSession.close();后,才能从二级缓存中查数据
    • 解决:https://blog.csdn.net/sdfgegefdg/article/details/115741737

      • 二级缓存需要openSession.close();后,才能从二级缓存中查数据

      • 如果openSession.close();在第二次查询之后才关闭,则第二次查询会从一级缓存中查,如果不是一个session,则查询不到数据:

      • 说明:第二次又重新发送了sql,因为从二级缓存中取数据时,会话没关闭所以二级缓存中没数据,所以又去一级缓存中查询,也没有数据则发送了sql查数据库;

        所以,只有会话关闭或提交后一级缓存中的数据才会转移到二级缓存中,然后因为是同一个namespace所以可以获取到数据

Maven资源路径问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<build>
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>true</filtering>
</resource>

<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>true</filtering>
</resource>
</resources>
</build>