MyBatis

MyBatis是优秀的持久层框架,持久层

持久化就是将程序的数据在持久状态和瞬时状态的转化的过程。而内存有断电即失的特性,为了数据不丢失,需要持久化数据,而且内存也很珍贵。

持久层:Dao层,Service层,Controller层。。。完成持久化的代码块,其层界限明显。

MyBatis是为了方便JDBC,方便数据存储,方便后续优化。将sql语句独立出来。

3.5.6版本MyBatis的Maven依赖:

1
2
3
4
5
6
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.6</version>
</dependency>

第一个MyBatis程序

项目环境搭建省略。

官网:https://mybatis.org/mybatis-3/zh/index.html


基本配置(Mapper配置不要忘)

  • 核心xml配置文件(文件路径用/而不是.)

    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"/>
    <dataSource type="POOLED">
    <property name="driver" value="com.mysql.jdbc.Driver"/>
    <property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=true&amp;useUnicode=true&amp;characterEncoding=UTF-8"/>
    <property name="username" value="root"/>
    <property name="password" value="111111"/>
    </dataSource>
    </environment>
    </environments>
    <!-- 每个Mapper.xml文件都需要在核心配置中注册配置 -->
    <mappers>
    <mapper resource="com/tang/dao/UserMapper.xml"/>
    </mappers>
    </configuration>
  • 工具类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    public class MyBatisUtils {
    private static SqlSessionFactory sqlSessionFactory;
    static {
    try {
    //获得SqlSessionFactory对象
    String resource = "mybatis-config.xml";
    InputStream inputStream = Resources.getResourceAsStream(resource);
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    } catch (IOException e) {
    e.printStackTrace();
    }
    }

    public static SqlSession getSqlSession(){
    return sqlSessionFactory.openSession();
    }
    }

代码

  • 实体类

    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
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    public class User {
    private int id;
    private String username;
    private String password;

    public User() {
    }

    public User(int id, String username, String password) {
    this.id = id;
    this.username = username;
    this.password = password;
    }

    public int getId() {
    return id;
    }

    public void setId(int id) {
    this.id = id;
    }

    public String getUsername() {
    return username;
    }

    public void setUsername(String username) {
    this.username = username;
    }

    public String getPassword() {
    return password;
    }

    public void setPassword(String password) {
    this.password = password;
    }

    @Override
    public String toString() {
    return "User{" +
    "id=" + id +
    ", username='" + username + '\'' +
    ", password='" + password + '\'' +
    '}';
    }
    }

  • Dao接口

    1
    2
    3
    public interface UserDao {
    List<User> getUserList();
    }
  • 接口实现(mapper.xml)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    <?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/Dao接口-->
    <mapper namespace="com.tang.dao.UserDao">
    <!-- 查询语句 -->
    <select id="getUserList" resultType="com.tang.pojo.User">
    select * from mybatis.user
    </select>
    </mapper>

测试(约定大于配置!)

  • The error may exist in com.tang.dao/UserMapper.xml

    Maven约定大于配置,要配置识别文件的代码在pom里

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    public class UserDaoTest {
    @Test
    public void test(){
    SqlSession sqlSession = MyBatisUtils.getSqlSession();
    UserDao userDao = sqlSession.getMapper(UserDao.class);
    List<User> userList = userDao.getUserList();
    for(User user : userList){
    System.out.println(user);
    }
    sqlSession.close();
    }
    }

注意点

  • mybatis的配置文件中,要为每一个mapper声明,也就是每个sql语句文件都要声明
  • 当我们Mapper.xml书写不规范,例如写在资源文件夹以外的文件夹中,由于Maven约定大于配置,它不会编译其他文件夹中的xml,这时我们要去pom.xml中声明相关resource,最好父子项目均声明

CURD(mapper.xml)

语句基本配置

namespace应和dao目录下mapper文件一致

id:使用namespace中的方法

resultType(select独占sqlSession.close();):使用id对应方法的返回值(工具类封装好了get、set)

parameterType:方法的参数类型

注意!增删改一定要提交事务:sqlSession.commit()

步骤

  • 编写接口类(注意增删改的返回类型,它会返回被影响数据的个数,所以是int型)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    public interface UserMapper {
    /**查询所有用户,返回用户列表*/
    List<User> getUserList();

    /**ID查询用户*/
    User getUserById(int id);

    /**insert会返回查询行数,故设为int型*/
    int addUser(User user);

    int updateUser(User user);

    int deleteUser(int id);
    }
  • 在mapper文件中编写对应的sql语句

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    <mapper namespace="com.tang.dao.UserMapper">

    <select id="getUserList" resultType="com.tang.pojo.User">
    select * from mybatis.user;
    </select>

    <select id="getUserById" parameterType="int" resultType="com.tang.pojo.User">
    select * from mybatis.user where id = #{id}
    </select>

    <insert id="addUser" parameterType="com.tang.pojo.User">
    insert into mybatis.user (id,username,password) values (#{id},#{username},#{password});
    </insert>

    <update id="updateUser" parameterType="com.tang.pojo.User">
    update mybatis.user set username = #{username},password = #{password} where id = #{id}
    </update>

    <delete id="deleteUser" parameterType="int">
    delete from mybatis.user where id = #{id}
    </delete>
    </mapper>
  • 测试(注意一定增删改要提交事务

    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
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    public class UserDaoTest {
    @Test
    public void test(){
    SqlSession sqlSession = MyBatisUtils.getSqlSession();
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    List<User> userList = mapper.getUserList();
    for(User user : userList){
    System.out.println(user);
    }
    sqlSession.close();
    }

    @Test
    public void getUserById(){
    SqlSession sqlSession = MyBatisUtils.getSqlSession();

    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    User user = mapper.getUserById(1);
    System.out.println(user);

    sqlSession.close();
    }

    //增删改要提交事务
    @Test
    public void addUser(){
    SqlSession sqlSession = MyBatisUtils.getSqlSession();

    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    mapper.addUser(new User(4,"她她她","444"));

    sqlSession.commit();
    sqlSession.close();
    }

    @Test
    public void updateUser(){
    SqlSession sqlSession = MyBatisUtils.getSqlSession();

    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    mapper.updateUser(new User(4,"地导弹","555"));

    sqlSession.commit();
    sqlSession.close();
    }

    @Test
    public void deleteUser(){
    SqlSession sqlSession = MyBatisUtils.getSqlSession();

    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    mapper.deleteUser(4);

    sqlSession.commit();
    sqlSession.close();
    }
    }

使用Map

使用map操作参数,可自行操作变量,不用按数据库规定

1
2
3
4
<!-- 使用map,可自定义变量名 -->
<insert id="addUser2" parameterType="map">
insert into mybatis.user (id,username,password) values (#{userid},#{name},#{pwd});
</insert>

测试时,map直接放入键值对,对应mapper的配置就行,这就不用受数据库的限制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Test
public void addUser2(){
SqlSession sqlSession = MyBatisUtils.getSqlSession();

UserMapper mapper = sqlSession.getMapper(UserMapper.class);
Map<String, Object> map = new HashMap<String, Object>();
map.put("userid",5);
map.put("name","yyy");
map.put("pwd","666");
mapper.addUser2(map);

//一定要提交事务
sqlSession.commit();
sqlSession.close();
}

模糊查询

1
2
3
<select id="getUserLike" resultType="com.tang.pojo.User">
select * from mybatis.user where username like "%"#{value}"%"
</select>

必须要在传递数据的添加调配符%,因为一个%就指代其他任意字符,我们这就相当于拼接字符串,举个例子就是X王X,2、3个字中带有王就可以查出,但Oracle数据库中就不能这样写。一般通配符写在mapper配置文件中就行,就不在java执行时写通配符,执行时传val的值即可

参考:https://blog.csdn.net/zhenwei1994/article/details/81876278


配置解析

核心配置文件

官网:https://mybatis.org/mybatis-3/zh/configuration.html#

1
2
3
4
5
6
7
8
9
10
11
12
13
configuration(配置)
- properties(属性)
- settings(设置)
- typeAliases(类型别名)
- typeHandlers(类型处理器)
- objectFactory(对象工厂)
- plugins(插件)
- environments(环境配置)
- environment(环境变量)
- transactionManager(事务管理器)
- dataSource(数据源)
- databaseIdProvider(数据库厂商标识)
- mappers(映射器)

环境配置(environments)

环境可配置多个,但每次只能选择一个使用

1
2
3
4
5
6
7
8
9
10
11
12
13
<environments default="development">
<environment id="development">
<transactionManager type="JDBC">
<property name="..." value="..."/>
</transactionManager>
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
  • transactionManager:JDBC | MANAGED

  • dataSource:dbcp、c3p0等等,连接数据库的

MyBatis默认事务管理器是JDBC,连接池是POOLED(池的概念)

属性(properties)

可通过properties属性来被其他配置文件引用

这些属性可以在外部进行配置,并可以进行动态替换。你既可以在典型的 Java 属性文件中配置这些属性,也可以在 properties 元素的子元素中设置。(db.properties)

db.properties(外部配置文件)

1
2
3
4
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=UTF-8
username=root
password=111111

在核心配置文件中引用

1
2
3
4
5
6
7
8
9
10
11
12
13
<properties resource="db.properties"/>

<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>

我们也可以在properties标签中添加一些属性配置,但同名配置外部配置文件优先。

设置(settings)

重要的调整设置,看官网吧,太多了。

设置名 描述 有效值 默认值
cacheEnabled 全局性地开启或关闭所有映射器配置文件中已配置的任何缓存。 true | false true
lazyLoadingEnabled 延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。 特定关联关系中可通过设置 fetchType 属性来覆盖该项的开关状态。 true | false false
logImpl 指定 MyBatis 所用日志的具体实现,未指定时将自动查找。 SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING 未设置

类型别名(typeAliases)

类型别名可为 Java 类型设置一个缩写名字。 它仅用于 XML 配置,意在降低冗余的全限定类名书写。

法一:可直接给实体类起别名

1
2
3
<typeAliases>
<typeAlias type="com.tang.pojo.User" alias="User"/>
</typeAliases>

法二:也可以指定一个包名,MyBatis 会在包名下面搜索需要的 Java Bean,即定义实体类在的包,然后在Mapper中使用Java Bean时可直接使用其首字母小写形式作为别名

1
2
3
4
<typeAliases>
<package name="com.tang.pojo"/>
</typeAliases>
<!-- Mapper使用时直接写user -->
  • 实体类较少建议使用1,多的时候用2
  • 法一可直接自定义别名,法二也可通过注解自定义别名

特殊别名

基本类型和一些数据结构有特殊的别名

别名 映射的类型
_byte byte
_long long
_short short
_int int
_integer int
_double double
_float float
_boolean boolean
string String
byte Byte
long Long
short Short
int Integer
integer Integer
double Double
float Float
boolean Boolean
date Date
decimal BigDecimal
bigdecimal BigDecimal
object Object
map Map
hashmap HashMap
list List
arraylist ArrayList
collection Collection
iterator Iterator

映射器(mappers)有细节注意点

方法1:配置资源路径

1
2
3
<mappers>
<mapper resource="com/tang/dao/UserMapper.xml"/>
</mappers>

方法2:使用class绑定接口类

  • 注意!!!接口和Mapper配置文件必须同名
  • 接口和Mapper配置文件必须位于同一包下
1
2
3
<mappers>
<mapper class="com.tang.dao.UserMapper"/>
</mappers>

方法3:扫描包进行注入绑定

  • 注意!!!接口和Mapper配置文件必须同名
  • 接口和Mapper配置文件必须位于同一包下
1
2
3
<mappers>
<package name="com.tang.dao"/>
</mappers>

生命周期和作用域

生命周期和作用域的错误使用会导致严重的并发问题

  • SqlSessionFactory(局部变量)

    一旦创建了 SqlSessionFactory,就不再需要它了。 因此 SqlSessionFactoryBuilder 实例的最佳作用域是方法作用域(也就是局部方法变量)。

  • SqlSessionFactory(应用作用域,全局变量)

    SqlSessionFactory一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建另一个实例。类比数据库连接池。推荐使用单例模式

  • SqlSession(方法作用域)

    每个线程都应该有它自己的 SqlSession 实例。SqlSession 的实例不是线程安全的,因此是不能被共享的。 用完就关,以防资源占用。

ResultMap(自定义映射关系)

当工具类中的和我们的数据库属性不一致时

1
2
3
4
private int id;
private String username;
//数据库中为password
private String pwd;
1
2
查询结果:
User{id=1, username='汤承', pwd='null'}

解决办法:ResultMap结果集映射

1
2
3
4
<resultMap id="UserMap" type="user">
<!-- column对应数据库,property对应工具栏的自定义 -->
<result column="password" property="pwd"/>
</resultMap>
1
2
查询结果:
User{id=1, username='汤承', pwd='111'}

日志

以前报错看debug,现在选择多看日志。

  • SLF4J
  • LOG4J
  • JDK_LOGGING
  • COMMONS_LOGGING
  • STDOUT_LOGGING
  • NO_LOGGING

mybatis具体使用哪个自行设置

STDOUT_LOGGING 标准日志输出

1
2
3
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>

日志反馈

1
2
3
4
5
6
7
8
9
10
11
12
Opening JDBC Connection
Created connection 1765250898.
Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@69379752]
==> Preparing: select * from mybatis.user where id = ?
==> Parameters: 1(Integer)
<== Columns: id, username, password
<== Row: 1, 汤承, 111
<== Total: 1
User{id=1, username='汤承', pwd='111'}
Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@69379752]
Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@69379752]
Returned connection 1765250898 to pool.

LOG4J(推荐使用)

通过使用Log4j,我们可以控制日志信息输送的目的地;控制每一条日志的输出格式;通过定义每一条日志信息的级别,能更加细致地控制日志的生成过程。这些都可以通过一个配置文件来灵活地进行配置,而不需要修改应用的代码。

  • 先导包

    1
    2
    3
    4
    5
    <dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.17</version>
    </dependency>
  • 配置log4j.properties

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    #将等级为DEBUG的日志信息输出到console和file这两个目的地,console和file的定义在下面的代码
    log4j.rootLogger=DEBUG,console,file

    #控制台输出的相关设置
    log4j.appender.console = org.apache.log4j.ConsoleAppender
    log4j.appender.console.Target = System.out
    log4j.appender.console.Threshold=DEBUG
    log4j.appender.console.layout = org.apache.log4j.PatternLayout
    log4j.appender.console.layout.ConversionPattern=[%c]-%m%n

    #文件输出的相关设置
    log4j.appender.file = org.apache.log4j.RollingFileAppender
    log4j.appender.file.File=./log/kuang.log
    log4j.appender.file.MaxFileSize=10mb
    log4j.appender.file.Threshold=DEBUG
    log4j.appender.file.layout=org.apache.log4j.PatternLayout
    log4j.appender.file.layout.ConversionPattern=[%p][%d{yy-MM-dd}][%c]%m%n

    #日志输出级别
    log4j.logger.org.mybatis=DEBUG
    log4j.logger.java.sql=DEBUG
    log4j.logger.java.sql.Statement=DEBUG
    log4j.logger.java.sql.ResultSet=DEBUG
    log4j.logger.java.sql.PreparedStatement=DEBUG
  • 核心配置文件中设置日志

    1
    2
    3
    <settings>
    <setting name="logImpl" value="LOG4J"/>
    </settings>
  • 使用

    • 导入org.apache.log4j.Logger,包别导错了

    • 生成日志对象,参数为当前类class

      1
      Logger logger = Logger.getLogger(UserDaoTest.class)
    • 使用logger对象来调试(就像之前做安卓,用日志语句输出,可以观测哪一些语句执行了,哪些没执行,进而找到问题出错的地方)

      1
      2
      3
      4
      5
      6
      @Test
      public void testLog4j(){
      logger.info("info:调用testLog4j方法");
      logger.debug("debug:调用tesLog4j方法");
      logger.error("error:调用testLog4j方法");
      }

分页

使用sql语句limit分页

  • 接口

    1
    List<User> getUserLimit(Map<String,Integer> map);
  • 配置mapper

    1
    2
    3
    <select id="getUserLimit" parameterType="map" resultMap="UserMap">
    select * from mybatis.user limit #{startIndex},#{pageSize}
    </select>
  • 测试

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    @Test
    public void getUserLimit(){
    SqlSession sqlSession = MyBatisUtils.getSqlSession();

    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    Map<String, Integer> map = new HashMap<String, Integer>();
    map.put("startIndex",0);
    map.put("pageSize",3);
    List<User> user = mapper.getUserLimit(map);
    for(User u : user)
    System.out.println(u);

    sqlSession.close();
    }

MyBatis PageHelper分页插件:https://pagehelper.github.io/


注解开发

基本流程

  • 面向接口编程:解耦

  • 接口和实现分离

这两点我有点联想到设计模式

反射机制+动态代理

使用注解来映射简单语句会使代码显得更加简洁,但对于稍微复杂一点的语句,Java 注解不仅力不从心,还会让你本就复杂的 SQL 语句更加混乱不堪。 因此,如果你需要做一些很复杂的操作,最好用 XML 来映射语句。

  • 定义接口

    1
    2
    @Select("select * from user")
    List<User> getUser();
  • 配置Mapper

    1
    2
    3
    4
    <!-- 绑定接口 -->
    <mappers>
    <mapper class="com.tang.dao.UserMapper"/>
    </mappers>
  • 测试

    1
    2
    3
    4
    5
    6
    7
    8
    9
    @Test
    public void getUser(){
    SqlSession sqlSession = MyBatisUtils.getSqlSession();
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    List<User> userList = mapper.getUser();
    for(User user : userList)
    System.out.println(user);
    sqlSession.close();
    }

MyBatis执行流程

在MyBatisUtils中

  • 先获得核心配置文件

    1
    2
    String resource = "mybatis-config.xml";
    InputStream inputStream = Resources.getResourceAsStream(resource);
  • 隐式的调用方法加载配置文件,build方法内部如图

    1
    sqlSessionFactory  = new SqlSessionFactoryBuilder().build(inputStream);

    然后sqlSessionFactory便被实例化

  • 在测试、使用中调用接口,进而使用executor执行器(包括事务操作+CURD)

    1
    2
    SqlSession sqlSession = MyBatisUtils.getSqlSession();
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);

blog11.png

注解操作CURD

  • 可在工具类实现自动提交事务,调用构造器自动提交

    1
    2
    3
    public static SqlSession getSqlSession(){
    return sqlSessionFactory.openSession(true);
    }

    实现接口后的对应方法:

    1
    2
    3
    public SqlSession openSession(boolean autoCommit) {
    return this.openSessionFromDataSource(this.configuration.getDefaultExecutorType(), (TransactionIsolationLevel)null, autoCommit);
    }
  • 接口添加注解(接口要及时注册到核心配置文件)

    1
    2
    3
    4
    5
    @Select("select * from user where id=#{id}")
    User getUserId(@Param("id") int id);

    @Insert("insert into user(id,username,password) values (#{id},#{username},#{password})")
    int addUser(User user);

    @Param:基本类型&String需要加上、sql中优先使用这里设置的属性名。

Lombok

相关评议:https://www.zhihu.com/question/42348457

就是一个偷懒工具,JavaBean中再也不用写get、set、toString以及构造器,通过注解即可搞定。

步骤:先在idea中安装插件,maven加入依赖,字段加入注解。

maven

1
2
3
4
5
6
7
8
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.20</version>
<scope>provided</scope>
</dependency>
</dependencies>

可使用的注解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Getter and @Setter
@FieldNameConstants
@ToString
@EqualsAndHashCode
@AllArgsConstructor, @RequiredArgsConstructor and @NoArgsConstructor
@Log, @Log4j, @Log4j2, @Slf4j, @XSlf4j, @CommonsLog, @JBossLog, @Flogger, @CustomLog
@Data
@Builder
@SuperBuilder
@Singular
@Delegate
@Value
@Accessors
@Wither
@With
@SneakyThrows
@val
@var
experimental @var
@UtilityClass

工具类偷懒:

1
2
3
4
5
6
7
8
@Data
@AllArgsConstructor//有参构造
@NoArgsConstructor//无参,也可自行构造无参
public class User {
private int id;
private String username;
private String password;
}

多对一、一对多(这个要多练,有点懵)

sql表创建,环境搭建

关联(多对一)

集合(一对多)

  • sql语句,通过tid使学生与老师进行多对一的关联
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
CREATE TABLE `teacher` (
`id` INT(10) NOT NULL,
`name` VARCHAR(30) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8

INSERT INTO teacher(`id`, `name`) VALUES ('1', '秦老师');

CREATE TABLE `student` (
`id` INT(10) NOT NULL,
`name` VARCHAR(30) DEFAULT NULL,
`tid` INT(10) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `fktid` (`tid`),
CONSTRAINT `fktid` FOREIGN KEY (`tid`) REFERENCES `teacher` (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8

INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('1', '小明', '1');
INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('2', '小红', '1');
INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('3', '小张', '1');
INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('4', '小李', '1');
INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('5', '小王', '1');
  • 导入lombok辅助实体类Teacher、Student(偷懒)
  • 创建Mapper接口及其对应Mapper.xml
  • MyBatis核心配置文件注册Mapper

association(多对一)

如果我们查询到时候只写了返回resultType类型,那么它只会返回对应的实体类,只是一个字段名,但我们要查的是老师这个对象,这时就要我们自定义。

其实重要的只有association语句,其他的result标签和resultType是一样的。

1
2
3
<select id="getStudent" resultType="Student">
select * from student;
</select>

自定义resultMap查询

  • 方法一:查询嵌套。子查询,先查找学生表,再查找老师表中的对应信息进行赋值。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<!-- 查询所有学生信息,根据查询到学生tid,再查找对应的老师 
查询学生,自定义一个返回类型,我们会在里面关联老师返回对象 -->
<select id="getStudent" resultMap="studentTeacher">
select * from student;
</select>
<!-- 自定义的映射关系,返回一个Student pojo类的类型 -->
<resultMap id="studentTeacher" type="Student">
<result property="id" column="id"/>
<result property="name" column="name"/>
<!--
result只能简单映射一个属性,我们不能映射对象
对象这种复杂属性需要使用association
工具类中的teacher对象对应数据库的tid列,其java类型是Teacher这个类
然后通过select再嵌套一个查询语句,将对象里面的id和tid连接上
-->
<association property="teacher" column="tid" javaType="Teacher" select="getTeacher"/>
</resultMap>
<!-- 也就是说通过association关联对象,然后嵌套查询语句将对象里的值与数据库的列对应-->
<select id="getTeacher" resultType="Teacher">
select * from teacher where id = #{id};
</select>
  • 方法二:结果嵌套
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<!-- 结果嵌套 -->
<select id="getStudent2" resultMap="studentTeacher2">
SELECT s.`id` sid,s.`name` sname,t.`id` tid,t.`name` tname
FROM `student` s,`teacher` t
WHERE s.`tid` = t.`id`;
</select>

<!--
先查学生表,type=Student,然后开始关联属性
其中老师是一个类,比较复杂,我们需要使用association
-->
<resultMap id="studentTeacher2" type="Student">
<id property="id" column="sid"/>
<result property="name" column="sname"/>
<!--
要在老师对象里对应学生的teacher属性
将老师的id、name和tid、tname对应起来
-->
<association property="teacher" javaType="Teacher">
<id property="id" column="tid"/>
<result property="name" column="tname"/>
</association>
</resultMap>

两种查询方法,子查询是先查一个表,然后再关联另一个表查询对应字段属性;而结果查询是先把完整的查找语句打出来,然后自行定义其中的字段关系。

collection(一对多)

  • 方法一:子查询
1
2
3
4
5
6
7
8
9
10
11
12
13
<select id="getTeacher2" resultMap="TeacherStudent2">
select * from teacher where id=#{tid}
</select>

<resultMap id="TeacherStudent2" type="Teacher">
<id property="id" column="id"/>
<!-- 注意和子查询不同,这里要写一下使用的存储类型即List -->
<collection property="studentList" column="id" javaType="ArrayList" ofType="Student" select="getStudent"/>
</resultMap>

<select id="getStudent" resultType="Student">
select * from student where tid=#{tid}
</select>
  • 方法二:结果嵌套
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<select id="getTeacher" resultMap="TeacherStudent">
select t.id tid,t.name tname,s.id sid,s.name sname
from student s,teacher t
where s.tid=t.id and t.id=#{tid};
</select>

<resultMap id="TeacherStudent" type="Teacher">
<id property="id" column="tid"/>
<result property="name" column="tname"/>
<!--
这里和一对多不一样,使用了集合
而集合的java类型是List+泛型组成
不使用javaType,而用ofType
-->
<collection property="studentList" ofType="Student">
<id property="id" column="sid"/>
<result property="name" column="sname"/>
<result property="tid" column="tid"/>
</collection>
</resultMap>

注意

JavaType和ofType都是用来指定对象类型的,但是JavaType是用来指定pojo中属性的类型,而ofType指定映射到list集合属性中pojo的类型。


动态SQL

sql创建表单,然后自行搭建测试环境

1
2
3
4
5
6
7
CREATE TABLE `blog`(
`id` VARCHAR(50) NOT NULL COMMENT '博客id',
`title` VARCHAR(100) NOT NULL COMMENT '博客标题',
`author` VARCHAR(30) NOT NULL COMMENT '博客作者',
`create_time` DATETIME NOT NULL COMMENT '创建时间',
`views` INT(30) NOT NULL COMMENT '浏览量'
)ENGINE=INNODB DEFAULT CHARSET=utf8;

IF

可以发现,通过if进行条件过滤,实行动态sql语句,当我们存入author相关属性,则会定向到查询对应author,其他字段也是如此。

Mapper.xml

1
2
3
4
5
6
7
8
9
10
<!-- if语句若通过则执行其中内容--> 
<select id="queryBlogIF" parameterType="map" resultType="blog">
select * from mybatis.blog where 1=1
<if test="title!=null">
and title = #{title}
</if>
<if test="author!=null">
and author = #{author}
</if>
</select>

测试类

1
2
3
4
5
6
7
8
9
10
11
@Test
public void queryBlogIF(){
SqlSession sqlSession = MyBatisUtils.getSqlSession();
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
Map map = new HashMap();
map.put("author","TangCheng");
List<Blog> blogs = mapper.queryBlogIF(map);
for(Blog b : blogs)
System.out.println(b);
sqlSession.close();
}

choose、when、otherwise

choose、when、otherwise类似switch、case、default,

不同点:没有输出的sql拼接语句长这样,会view=?,这样就查不出结果了

1
select * from mybatis.blog WHERE views = ?

Mapper.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<select id="queryBlogChoose" parameterType="map" resultType="blog">
select * from mybatis.blog
<where>
<choose>
<when test="title!=null">
title = #{title}
</when>
<when test="author!=null">
and author = #{author}
</when>
<otherwise>
and views = #{views}
</otherwise>
</choose>
</where>
</select>

测试类

1
2
3
4
5
6
7
8
9
10
11
@Test
public void queryBlogChoose(){
SqlSession sqlSession = MyBatisUtils.getSqlSession();
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
Map map = new HashMap();
map.put("views","9999");
List<Blog> blogs = mapper.queryBlogChoose(map);
for(Blog b : blogs)
System.out.println(b);
sqlSession.close();
}

trim、where、set

  • where

    首先说说where,可以看上面的if和choose进行对比,if中没有使用where标签,和后面语句的衔接明显有问题,还必须加入1=1这个废句子来强行拼接合理,而choose中使用了where标签就顺滑多了。

    where元素只会在有返回内容时下才插入 “WHERE”的sql 子句,在choose中若没写otherwise且无when判断成功,则不会输出“WHERE”。当第一个子句的开头为 “AND” 或 “OR”时,where 元素也会将它们去除。

  • set(用于更新)

    因为update ··· set,set用于sql更新语句的。

    set标签会在有内容时加入set,并删除多余的尾部逗号,因为sql更新属性时,属性间用逗号隔开,最后一个无逗号。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    <update id="updateBlog" parameterType="map">
    update mybatis.blog
    <set>
    <if test="title!=null">
    title=#{title},
    </if>
    <if test="author!=null">
    author=#{author},
    </if>
    </set>
    where id=#{id}
    </update>
  • trim(自定义)

    prefix前缀,prefixOverrides前缀覆盖

    suffix后缀,suffixOverrides后缀覆盖

    1
    2
    3
    4
    5
    6
    7
    8
    9
    <!-- 类似where,可替代where标签-->
    <trim prefix="WHERE" prefixOverrides="AND |OR ">
    ...
    </trim>

    <!-- 类似set,可替代set标签-->
    <trim prefix="SET" suffixOverrides=",">
    ...
    </trim>

Foreach(遍历)

Mapper.xml

集合、集合项、开头。分隔符、结尾

1
2
3
4
5
6
7
8
<select id="queryBlogForeach" parameterType="map" resultType="blog">
select * from mybatis.blog
<where>
<foreach collection="list" item="id" open="and (" separator="or" close=")">
id=#{id}
</foreach>
</where>
</select>

测试类:传入一个list,然后select中会遍历该list的id并拼接

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Test
public void queryBlogForeach(){
SqlSession sqlSession = MyBatisUtils.getSqlSession();
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
Map map = new HashMap();
List<Integer> list = new ArrayList<Integer>();
list.add(1);
list.add(2);
list.add(3);
map.put("list",list);

List<Blog> blogs = mapper.queryBlogForeach(map);
for(Blog b : blogs)
System.out.println(b);
sqlSession.close();
}

SQL片段

提取重复sql语句,变为sql片段,然后在要使用时引用

  • 使用sql标签提取公共片段

    1
    2
    3
    4
    5
    6
    7
    8
    <sql id="if">
    <if test="title!=null">
    title = #{title}
    </if>
    <if test="author!=null">
    and author = #{author}
    </if>
    </sql>
  • 引用时使用include标签,refid对应片段id

    1
    2
    3
    4
    5
    6
    <select id="queryBlogIF" parameterType="map" resultType="blog">
    select * from mybatis.blog
    <where>
    <include refid="if"></include>
    </where>
    </select>

小结

动态SQL就是加入了一些选择性的判断语句。而且我们在sql中常常需要注意标点符号的使用,多写漏写都会报错,使用动态sql就省去了省查符号的步骤,总的来说还是为了方便自身。


缓存

存在内存中的临时数据,不用每次都去数据库查询,直接在缓存中查询,提高查询效率,解决高并发等性能问题。常常要查询且不常改变的数据应使用缓存。

默认的清除策略:

  • LRU – 最近最少使用:移除最长时间不被使用的对象。
  • FIFO – 先进先出:按对象进入缓存的顺序来移除它们。
  • SOFT – 软引用:基于垃圾回收器状态和软引用规则移除对象。
  • WEAK – 弱引用:更积极地基于垃圾收集器状态和弱引用规则移除对象。

一级缓存(本地缓存)

一级缓存即本地缓存:SqlSession,默认开启

与数据库同一次会话期间查询到的数据会放在本地缓存。在SqlSession未关闭期间,若获得相同数据直接从缓存中拿取,不要再查询数据库。

  • 测试

    setting中要配置日志查看反馈

    测试在一次Session中查询两次相同的sql,发现只查一次sql语句,但两次都有结果,因为默认开启一级缓存,第二次查询直接加载第一次的。

  • 缓存失效:
    查询不同数据。

    中途有增删改操作,可能会改变原有数据,缓存刷新。

    手动清理。

一级缓存默认开启,只在一次SqlSession开启到关闭间有效。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Test
public void test(){
SqlSession sqlSession = MyBatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user = mapper.queryUserId(1);
System.out.println(user);
/*
*中途增删改,清空缓存:mapper.updateUser(new User(2,"2","2"));
*手动清空缓存:sqlSession.clearCache();
*/
User user2 = mapper.queryUserId(1);
System.out.println(user2);
sqlSession.close();
}

二级缓存(全局缓存)

  • 机制
    • 一个会话查询一条数据,数据会放在当前会话的一级缓存
    • 会话关闭后,该会话一级缓存就消失了;但可以把该一级缓存保存到二级缓存中
    • 新会话的查询信息,可以从二级缓存中获取内容

首先要开启全局缓存

1
<setting name="cacheEnabled" value="true"/>

使用二级缓存,开启二级缓存,还可以自定义一些属性

1
2
3
4
5
6
<cache/>

<cache eviction="FIFO"
flushInterval="60000"
size="512"
readOnly="true"/>

注意pojo实体类要实现Serializable接口,将实体类序列化,否则使用单cache标签时会报错

1
Cause: java.io.NotSerializableException: com.tang.pojo.User

测试:

在关闭第一个SqlSession后,由于开启了二级缓存,SqlSession1仍会拿到缓存不用查询数据库

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Test
public void test(){
SqlSession sqlSession = MyBatisUtils.getSqlSession();
SqlSession sqlSession1 = MyBatisUtils.getSqlSession();

UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user = mapper.queryUserId(1);
System.out.println(user);
sqlSession.close();

UserMapper mapper1 = sqlSession1.getMapper((UserMapper.class));
User user1 = mapper1.queryUserId(1);
/*
*增删改会清空Mapper的二级缓存:mapper1.updateUser(new User(2,"1","1"));
*/
System.out.println(user1);
sqlSession1.close();
}

查询:二级缓存————》一级缓存————》数据库

自定义缓存-ehcache(现在用Redis数据库来缓存)

maven导包

1
2
3
4
5
6
<!-- https://mvnrepository.com/artifact/org.mybatis.caches/mybatis-ehcache -->
<dependency>
<groupId>org.mybatis.caches</groupId>
<artifactId>mybatis-ehcache</artifactId>
<version>1.2.1</version>
</dependency>

Mapper配置

1
<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>

配置文件ehcache.xml

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
<?xml version="1.0" encoding="UTF-8" ?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"
updateCheck="false">

<diskStore path="./tmpdir/Tmp_EhCache"/>

<defaultCache
eternal="false"
maxElementsInMemory="10000"
overflowToDisk="false"
diskPersistent="false"
timeToIdleSeconds="1800"
timeToLiveSeconds="259200"
memoryStoreEvictionPolicy="LRU"/>

<cache
name="cloud_user"
eternal="false"
maxElementsInMemory="5000"
overflowToDisk="false"
diskPersistent="false"
timeToIdleSeconds="1800"
timeToLiveSeconds="1800"
memoryStoreEvictionPolicy="LRU"/>
</ehcache>

还可以自行写一个类实现cache接口来实现自定义缓存


小结

下一步Spring MVC,学完SSM赶紧搞个小项目!!