MyBatis

67 阅读14分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

目录

一.介绍

1.MyBatis的特性

二.初识MyBatis的增删改查

1.预备步骤

 2.最简单的增删改查操作

1)插入数据 

2)更新数据

3)删除数据

4)查询数据

3.功能优化

1.自动提交

2.日志功能

三.简化MyBatis的操作

1.利用properties文件配置数据库信息

2.typeAliases   类型别名 

3.简化引入映射文件

4.设置核心配置文件的模板和工具类

四.实现动态的增删改查

1.在配置文件中获取参数的两种方式

2.根据传递参数的类型个数不同而获取参数的方式不同

1)4中情况 

2)将4中情况转化为两种情况

3.动态查询

1)普通动态查询 

2)特殊SQL处理

五.处理复杂关系

1.处理属性名和字段名不同的情况

2.处理多对一关系查询

3.处理一对多关系查询

4.延迟加载

六.动态SQL

七.缓存

1.一级缓存

2.二级缓存

八.逆向工程

九.其他功能

1.获取自增的主键  



​编辑

​编辑

一.介绍

MyBatis是一个基于Java的持久层框架,提供的持久层框架包括SQL Maps和Data Access Objects(DAO)。

持久层:存储在硬盘中,不会断电消失的数据。

SQL Maps:指的是数据库中的数据和Java数据的映射关系。也就是MyBatis封装JDBC的过程。

框架本质就是 jar包+配置文件。

1.MyBatis的特性

1.支持定制化SQL,存储过程以及高级映射的持久层框架

定制化SQL:SQL语句需要自己去写。

存储过程:是MySQL高级学习的内容

普通映射:利用JDBC访问数据库的过程中,自己封装了JDBC工具类,每当把数据从数据库中查询出来后,都会根据反射将它对应Java中的实体类对象,要求是字段名必须跟类的属性名保持一致,否则是映射不了的。

高级映射:实体类中的属性,通过SQL是查询不到的,比如一对多的关系,多对一的关系,这是表和表之间的关系,那么对应的Java中的实体类之间也需要有关系。用SQL查询出来的数据无法对应某个实体或实体的集合。

2.避免手动设置参数和获取结果集

3.可以使用简单的XML或注解用于配置和原始映射,将接口和Java的POJO映射成数据库中的记录。

 sql语句可以写到xml文件中,或写在注解中。

4.是一个半自动的ORM(Object Relation Mapping)框架

JDBC就是手动,所有的过程都要自己去写。

半自动就是封装了部分的功能,像sql还是需要自己写,也可以自己处理映射关系

ORM:对象关系映射,对象指的是Java实体对象,关系指的是关系型数据库,其实就是将Java中的实体类对象和关系型数据库中的数据创建映射关系。

二.初识MyBatis的增删改查

目录结构

​编辑

1.预备步骤

Step1:创建maven工程

​编辑

Step2:在pom.xml中添加mybatis相关的依赖

<dependencies>
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.7</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.27</version>
        </dependency>
    </dependencies>

Step3:在resources下面创建mybatis-config.xml文件(核心配置文件

​编辑

核心配置文件详解-配置关于mybatis的相关信息。

在ssm整合后,可以没有这个配置文件,核心配置文件中所配置的内容可以交给spring管理。

<?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>


<!--    引入资源文件-->
    <properties resource="jdbc.properties"/>

<!--设置类型别名-->
    <typeAliases>
<!--
用一个别名User,来代替全类名pojo.User,这个别名不区分大小写
如果不写alias属性,那么就会分配一个默认的别名,这个默认的别名就是类名且不区分大小写
所以写了个User和不写效果一样
-->
<!--        <typeAlias type="pojo.User" alias="User"></typeAlias>-->
<!--        以包为单位,给包下所有的类设置默认别名,这里给pojo下面所有的包都设置了默认别名且不区分大小写,这个比较常用-->
        <package name="pojo"/>
    </typeAliases>
<!--
配置链接数据库的环境
不管有多少个环境,也只能选择其中一个使用,default表示默认使用哪个环境
-->
    <environments default="development">
<!--
配置某个具体的环境每一个environment标签都是一个连接具体数据库的环境
id:表示连接数据库环境的一个唯一标识,不能重复
-->
        <environment id="development">
<!--
设置事务管理器的类型,JDBC类型需要手动提交事务
JDBC:表示当前环境中,执行SQL时,使用的是JDBC中原生的事务管理方式,原生指的是 提交就是commit,回滚就是rollback,事务的提交和回滚需要手动处理
MANAGED:被管理,例如被Spring事务
-->
            <transactionManager type="JDBC"/>
<!--
dataSource:配置数据源
    type:POOLED  设置数据源的类型,表示使用数据库连接池来缓存数据库连接
    type:UNPOOLED   表示不使用数据库连接池来缓存数据库连接
    type:JNDI   表示上下文中的数据源
ssm整合之后就不需要设置数据源了, spring提供数据源
-->
            <dataSource type="POOLED">
                <property name="driver" value="${jdbc.driver}"/>
                <property name="url" value="${jdbc.url}"/>
                <property name="username" value="${jdbc.username}"/>
                <property name="password" value="${jdbc.password}"/>
            </dataSource>
        </environment>
    </environments>








<!--    引入映射文件-->
    <mappers>
<!--        resources下面的不叫包(和包的图像不同),是具体的资源,包是装Java类的,所以这里要以路径的方式引入-->
<!--        <mapper resource="mappers/UserMapper.xml"/>-->
<!--        以包为单位引入映射文件时需要满足两点
            1.mapper接口所在的包要和映射文件所在的包一致,即包名要一样
            2.mapper接口要和映射文件的名字一致
            -->
        <package name="mapper"/>
    </mappers>
</configuration>

Step4:创建数据库mybatis和表t_user

​编辑

Step5:创建pojo类对应t_user表

属性要和字段名相同,如果字段名有_,那么属性名就用驼峰命名法,查询字段时时用别名查询。

public class User {
    //保证属性名和字段名一致
    private Integer id;
    private String username;
    private String password;
    private Integer age;
    private String sex;
    private String email;

    public User() {
    }

    public User(Integer id, String username, String password, Integer age, String sex, String email) {
        this.id = id;
        this.username = username;
        this.password = password;
        this.age = age;
        this.sex = sex;
        this.email = email;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer 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;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

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

Step6:创建mapper接口

现在在数据库中有了表,在Java中有了对应的对象。在Java中定义方法去操作表中的数据,就需要mapper来实现。

mapper就是JDBC中的DAO,一个mapper对应一个表。

//当调用接口中的方法,它会自动去匹配一个方法并且执行
public interface UserMapper {

    /*
    MyBatis面向接口编程的两个一致
    1.mapper接口的映射文件的namespace要和mapper接口的全类名保持一致
    2.映射文件中sql语句的id要和mapper接口中的方法名一致

    表-实体类-mapper接口-mapper映射文件 一一对应
     */
}

Step7:创建mapper的配置文件UserMapper.xml

​编辑

就像是mapper接口的实现类,来写具体方法内的sql语句。

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--通过命名空间找到方法所在的接口,再通过下面的方法名找到具体的方法-->
<mapper namespace="mapper.UserMapper">

</mapper>

Step8:在测试类中通过一系列步骤拿到mapper对象来执行mapper里面的方法

public class MyBatisTest {
    @Test
    public void testMyBatis() throws IOException {
        //加载核心配置文件
        InputStream resourceAsStream = Resources.getResourceAsStream("mybatis-config.xml");
        //获取sqlSessionFactoryBuilder
        SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
        //获取sqlSessionFactory对象
        SqlSessionFactory factory = builder.build(resourceAsStream);
        //获取SqlSession,sqlSession代表Java程序和数据库之间的会话。就像HttpSession是Java程序和浏览器之间的会话
        //SqlSession默认不自动提交事务,若需要自动提交事务可以在openSession方法中设置为true
        SqlSession sqlSession = factory.openSession(true);//设置true可以自动提交事务
        //获取mapper接口对象,这个方法的底层用到了代理模式,在底层为接口创建了实现类。
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        //测试功能,通过mapper调用接口中的方法
          
    }

2.最简单的增删改查操作

1)插入数据

Step1:在UserMapper接口中添加方法

    //添加用户信息
    int insertUser();

Step2:在UserMapper.xml中添加配置

<!--    int insertUser();-->
        <insert id="insertUser">
            insert into t_user values(null,'admin','123',23,'男','123@qq.com')
        </insert>

Step3:在测试类中测试

        int result = mapper.insertUser();
        //sqlSession.commit();//手动提交事务,如果前面openSession中设置了true可以省略该句
        System.out.println(result);

2)更新数据

Step1:

    //修改用户信息
    void updateUser();
    

Step2:

<!--    void updateUser();-->
        <update id="updateUser">
            update t_user set username = '张三' where id = 12;
        </update>

3)删除数据

Step1:

    //删除用户信息
    void deleteUser();

Step2:

<!--    void deleteUser();-->
        <delete id="deleteUser">
            delete from t_user where id=12;
        </delete>

4)查询数据

Step1:

    User getUserById();
    //查询所有用户信息
    List<User> getAllUser();

Step2:

<!--     User getUserById();-->
<!--    resultType:设置默认映射关系,自动将查询出的结果表的字段名来作为属性名来给对象赋值,如果匹配到就赋值,匹配不到就不赋值将查询出来的结果转换成设置好的结果类型,再将结果作为返回值返回到这个方法-->
<!--    resultMap:设置自定义的映射关系,处理字段名和属性名不一致的情况,还有一对多和多对一的关系-->
        <select id="getUserById" resultType="pojo.User">
            select * from t_user where id=13;
        </select>
<!--    List<User> getAllUser();-->
        <select id="getAllUser" resultType="pojo.User">
            select * from t_user;
        </select>

3.功能优化

1.自动提交

int result = mapper.insertUser();
sqlSession.commit();
/*
因为核心配置文件<transactionManager type="JDBC"/>设置为tyep,所以需要手动提交事务
在执行sql后提交
*/

2.日志功能

1.在pom.xml中添加依赖

<!--        日志信息-->
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.12</version>
        </dependency>

2.在resources下面创建log4j.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">

<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">

    <appender name="STDOUT" class="org.apache.log4j.ConsoleAppender">
        <param name="Encoding" value="UTF-8" />
        <layout class="org.apache.log4j.PatternLayout">
            <param name="ConversionPattern" value="%-5p %d{MM-dd HH:mm:ss,SSS} %m  (%F:%L) \n" />
        </layout>
    </appender>
    <logger name="java.sql">
        <level value="debug" />
    </logger>
    <logger name="org.apache.ibatis">
        <level value="info" />
    </logger>
    <root>
        <level value="debug" />
        <appender-ref ref="STDOUT" />
    </root>
</log4j:configuration>

3.测试增删改查功能时会输出日志信息

 运行程序的日志信息

​编辑

 row1:sql信息

row2:参数信息,显示可变参数

row3:受影响的行数

其他

 ​编辑

为什么会越来越详细?

如果DEBUG都需要打印日志信息的话,那么FATAL的提示肯定也要打印信息

三.简化MyBatis的操作

MyBatis中各种标签的添加顺序

The content of element type "configuration" must match "(properties?,settings?,typeAliases?,typeHandlers?,objectFactory?,

objectWrapperFactory?,reflectorFactory?,plugins?,environments?,

databaseIdProvider?,mappers?)".

1.利用properties文件配置数据库信息

Step1:在resources目录下创建jdbc.properties,存储连接数据库的信息

jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mybatis
jdbc.username=root
jdbc.password=niit

用jdbc.作为前缀是为了表明该键的功能,因为给键取名时是为了见名知意的,往往不同的properties文件的键会重名,用前缀来区分。

Step2:在核心配置文件中添加

<!--    引入资源文件-->
    <properties resource="jdbc.properties"/>

            <dataSource type="POOLED">
                <property name="driver" value="${jdbc.driver}"/>
                <property name="url" value="${jdbc.url}"/>
                <property name="username" value="${jdbc.username}"/>
                <property name="password" value="${jdbc.password}"/>
            </dataSource>

2.typeAliases   类型别名 

<!--设置类型别名-->
    <typeAliases>
<!--
用一个别名User,来代替全类名pojo.User,这个别名不区分大小写
如果不写alias属性,那么就会分配一个默认的别名,这个默认的别名就是类名且不区分大小写
所以写了个User和不写效果一样
-->
<!--        <typeAlias type="pojo.User" alias="User"></typeAlias>-->
<!--        以包为单位,给包下所有的类设置默认别名,这里给pojo下面所有的包都设置了默认别名且不区分大小写,这个比较常用-->
        <package name="pojo"/>
    </typeAliases>

在mapper的配置文件中,查询语句的结果需要返回resultType,如果不设置类型别名,如果想要将数据映射为对象,就需要写类的全类名路径。

        <select id="getAllUser" resultType="pojo.User">
            select * from t_user;
        </select>

如果定义了类型别名,就可以只写类名了

        <select id="getAllUser" resultType="User">
            select * from t_user;
        </select>

具体的类型别名的用法看上面注释

3.简化引入映射文件

<!--    引入映射文件-->
    <mappers>
<!--        resources下面的不叫包(和包的图像不同),是具体的资源,包是装Java类的,所以这里要以路径的方式引入-->
<!--        <mapper resource="mappers/UserMapper.xml"/>-->
<!--        以包为单位引入映射文件时需要满足两点
            1.mapper接口所在的包要和映射文件所在的包一致,即包名要一样
            2.mapper接口要和映射文件的名字一致
            -->
        <package name="mapper"/>
    </mappers>

4.设置核心配置文件的模板和工具类

1.核心配置文件的模板

​编辑

<?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>
    <properties resource="jdbc.properties"/>

    <typeAliases>
        <package name="pojo"/>
    </typeAliases>
    
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="${jdbc.driver}"/>
                <property name="url" value="${jdbc.url}"/>
                <property name="username" value="${jdbc.username}"/>
                <property name="password" value="${jdbc.password}"/>
            </dataSource>
        </environment>
    </environments>
    
    <mappers>
        <package name="mapper"/>
    </mappers>
</configuration>

2.mapper配置文件的模板

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="mapper.UserMapper">

</mapper>

3.获取SqlSession对象工具类

public class SqlSessionUtils {
    public static SqlSession getSqlSession(){
        SqlSession sqlSession = null;
        try {
            InputStream resourceAsStream = Resources.getResourceAsStream("mybatis-config.xml");
            SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
            SqlSessionFactory factory = builder.build(resourceAsStream);
            sqlSession = factory.openSession(true);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return sqlSession;
    }
}

四.实现动态的增删改查

在Mapper配置文件中获取接口中定义的参数,实现动态的进行增删改查。

1.在配置文件中获取参数的两种方式

方式1:${}

本质是字符串拼接,假如username的值为admin,那么

select * from t_user where username =${username} 对应sql语句为

select * from t_user where username = admin

这样查不到数据,需要查询条件的值为字符串类型,即username="admin"

所以需要这样写

select * from t_user where username ="${username}"

select * from t_user where username =‘${username}’

方式2:#{}

本质是占位符赋值,会自动添加引号

select * from t_user where username =#{username} 对应sql语句为

select * from t_user where username = ”admin"

2.根据传递参数的类型个数不同而获取参数的方式不同

1)4中情况

情况1:传递一个参数

User getUserByUsername(String username);

    <select id="getUserByUsername" resultType="User">
        select * from t_user where username = '${username}'
    </select>

    <select id="getUserByUsername" resultType="User">
        select * from t_user where username = #{username}
    </select>

当为一个参数时,括号内的参数名其实是可以随便写的,不写username写aaa也是可以的。

{}里面的内容是什么都不重要,可以任意取名,但最好见名知意

情况2:传递多个参数

User checkLogin(String username,String password);

<!--    User checkLogin(String username,String password);-->
<!--    当底层检测到方法有多个参数时,会自动把这些参数放到一个map集合里面,以arg0/param1,arg1/param2...为键,以参数值为值-->
<!--若mapper接口方法的参数有多个时,可以手动将这些参数放到一个map中存储-->
    <select id="checkLogin" resultType="User">
        select * from t_user where username = #{param1} and password = #{arg1}
    </select>

传递的多个参数会存到map集合中,每个值都对应两个键,比如map集合中第一个值对应的键就对应arg0或者param1.

情况3:传入一个map集合

User checkLoginByMap(Map<String,Object> map);

传入的map集合就替代了mybatis自动创建的map集合,可以从这个map集合中取值。

    <select id="checkLoginByMap" resultType="User">
        select * from t_user where username = #{username} and password = #{password}
    </select>

情况4:传入一个对象

int insertUser(User user);

    <insert id="insertUser">
        insert into t_user values (null,#{username},#{password},#{age},#{sex},#{email})
    </insert>

直接通过属性名获取对象中的数据

2)将4中情况转化为两种情况

通过注解的形式,将前三种情况转为一种情况。

User checkLoginByParam(@Param("username") String username, @Param("password") String password);

注解的作用是给键名命名,不用考虑键名根据参数的不同有多种情况了。

3.动态查询

1)普通动态查询

查询一个实体类对象

用对象(User)类型接收

User getUserById(@Param("id") int id);

<!--User getUserById(@Param("id") int id);-->
    <select id="getUserById" resultType="User">
        select * from t_user where id = #{id}
    </select>

用list集合(List)接收

List<User> getUserById(@Param("id") int id);

<!--User getUserById(@Param("id") int id);-->
    <select id="getUserById" resultType="User">
        select * from t_user where id = #{id}
    </select>

用map集合接收

查询到的内容就是属性名为键,属性值为值

Map<String,Object> getUserByIdToMap(@Param("id") int id);

    <select id="getUserByIdToMap" resultType="map">
        select * from t_user where id = #{id}
    </select>

查询返回一个集合

用list集合接收

List<User> getAllUser();

    <select id="getAllUser" resultType="User">
        select * from t_user
    </select>

用map集合接收

每个对象都是一个map集合,用list集合存储所有map集合

List<Map<String,Object>> getAllUserToMap();

<!--    Map<String,Object> getAllUserToMap();-->
    <select id="getAllUserToMap" resultType="map">
        select * from t_user
    </select>

也可以用注解

    //将查询出来的结果的id作为键,每行记录作为一个map集合,一个map集合作为值
    //{13={password=123, sex=男, id=13, age=23, email=123@qq.com, username=admin}, 15={password=123, sex=男, id=15, age=30, email=123@qq.com, username=李四}}
    @MapKey("id")
    Map<String,Object> getAllUserToMap();

用数据的唯一字段来作为数据的键名

查询返回一个值

    //查询用户信息总记录数
    int getCount();

<!--    这个写java.lang.Integer/Integer/integer/int/Int/_int 都可以,既然都可以那就说明这个地方写的是类型别名,是mybatis设置默认的类型别名-->
    <select id="getCount" resultType="java.lang.Integer">
        select count(*) from t_user
    </select>

分组查询  聚合查询查询结果是单行单列

聚合查询时设置查询结果是mybatis设置的类型别名

​编辑

常用的别名

​编辑

转换为map集合的好处

查询结果为Map集合 

有时页面中所需要的数据并不是从单纯从一个表中查询出来的,所以不能将所查询出来的数据单纯的对应一个类,所以可以放到Map集合中

Map集合响应到浏览器就是一个JSON对象

2)特殊SQL处理

模糊查询

 模糊查询

//根据用户名进行模糊查询用户信息
    List<User> getUserByLike(@Param("username") String username);

<!--List<User> getUserByLike(@Param("username") String username);-->
    <select id="getUserByLike" resultType="User">
<!--select * from t_user where username like '%#{username}%'-->
<!--'%?%'  此时模糊查询条件为这种,?会被当作字符串中的一部分,而不会当作是占位符,所以不会被赋值-->
       <!--解决方案1:select * from t_user where username like '%${username}%',-->
        <!--解决方案2:字符串拼接函数
        select * from t_user where username like concat('%',#{username},'%')
        -->
        <!--解决方案3:第三种最常用-->
        select * from t_user where username like "%"#{username}"%"
    </select>

    @Test
    public void likeTest(){
        SqlSession sqlSession = SqlSessionUtils.getSqlSession();
        SQLMapper mapper = sqlSession.getMapper(SQLMapper.class);
        List<User> list = mapper.getUserByLike("李");
        System.out.println(list);
    }

批量删除

比如邮件中,前面有复选框,将复选框全选中就可实现批量删除

    //批量删除
    int deleteMore(@Param("ids") String ids);

    <delete id="deleteMore">
<!--#{}是会自动加单引号的   delete from t_user where id in (‘1,2,3’)
这样是无法执行的,在sql中 in后面的不能加引号
delete from t_user where id in (13,14),当用$时,是这种情况
-->
        delete from t_user where id in (${ids})
    </delete>

    @Test
    public void deleteMoreTest(){
        SqlSession sqlSession = SqlSessionUtils.getSqlSession();
        SQLMapper mapper = sqlSession.getMapper(SQLMapper.class);
        int deleteMore = mapper.deleteMore("13,14");
        System.out.println(deleteMore);
    }

 用in 关键字,相当于or

批量删除只能用$, 不能用#

动态设置表名

    //查询指定表明中的数据
    List<User> getUserByTableName(@Param("tableName") String tableName);

<!--    List<User> getUserByTableName(@Param("tableName") String tableName);-->
<!--    要用${},因为表明不能加单引号-->
<select id="getUserByTableName" resultType="User">
    select * from ${tableName}
</select>

    @Test
    public void tableNameTest(){
        SqlSession sqlSession = SqlSessionUtils.getSqlSession();
        SQLMapper mapper = sqlSession.getMapper(SQLMapper.class);
        List<User> userByTableName = mapper.getUserByTableName("t_user");
        System.out.println(userByTableName);
    }

数据库中的表的数据太多会影响数据库的性能

将一张表切分,切分成多张表共同存储一张表中的数据

在操作数据时,所操作的表是不一样的,虽然表中存储的数据是同一张表的数据。

操作的表不一样,那么表名需要动态设置。

五.处理复杂关系

数据准备

新建t_employ表

​编辑

新建t_dept表

​编辑

不管是一对多还是多对一都需要在多的一方设置一的主键,所以要在在员工表中设置部门的did

新建Emp类

public class Emp {
    private int eid;

    private String empName;

    private int age;

    private String sex;

    private String email;

    private int did;
}

新建Dept类

public class Dept {
    private int did;

    private String deptName;
}

1.处理属性名和字段名不同的情况

方式1:起别名

//查询所有员工信息
List<Emp> getAllEmp();

    <select id="getAllEmp" resultType="Emp">
        select eid,emp_name empName,age,sex,email from t_employ
    </select>

方式2:通过settings设置mybatis的全局配置

将下划线自动映射为驼峰 

    <settings>
        <setting name="mapUnderscoreToCameCase" value="true"/>
    </settings>

方式3:设置结果映射

resultMap的id要写到select的resultMap属性中

    <resultMap id="empResultMap" type="Emp">
<!--        id字段来设置主键字段的映射关系-->
        <id property="eid" column="eid"></id>
<!--        result设置其他普通字段的映射关系-->
<!--        即使字段名和属性名相同 也得写-->
        <result property="empName" column="emp_name"></result>
        <result property="age" column="age"></result>
        <result property="sex" column="sex"></result>
        <result property="email" column="eamil"></result>
    </resultMap>
<!--    List<Emp> getAllEmp();-->
    <select id="getAllEmp" resultMap="empResultMap">
        select * from t_emp
    </select>

2.处理多对一关系查询

比如多个员工可以对应一个部门,在查询员工信息时,并显示该员工的部门信息。

员工为主要查询的信息,所以员工表为主表

Step1:在员工表中添加部门对象

public class Emp {
    private int eid;

    private String empName;

    private int age;

    private String sex;

    private String email;

    private int did;

    private Dept dept;
}

Step2:

方式1:级联属性赋值

将查询结果直接一一对应赋值

    //查询员工及员 工所在的部门信息
    Emp getEmpAndDept(int id);

<!--    Emp getEmpAndDept(int id);-->
<resultMap id="empAndDeptResultMap" type="Emp">
    <id property="eid" column="eid"></id>
    <result property="empName" column="emp_name"></result>
    <result property="age" column="age"></result>
    <result property="sex" column="sex"></result>
    <result property="email" column="email"></result>
<!--    查询出来的did字段和Emp中的dept属性的did属性映射-->
    <result property="dept.did" column="did"></result>
    <result property="dept.deptName" column="dept_name"></result>
</resultMap>

    <select id="getEmpAndDept" resultMap="empAndDeptResultTwo">
        select * from t_employ left join t_dept on t_employ.did = t_dept.did where t_employ.eid = #{eid}
    </select>

方式2:通过association标签,方式2的好处是比方式1清晰

    <resultMap id="empAndDeptResultTwo" type="Emp">
        <id property="eid" column="eid"></id>
        <result property="empName" column="emp_name"></result>
        <result property="age" column="age"></result>
        <result property="sex" column="sex"></result>
        <result property="email" column="email"></result>
<!--        javaType写的是属性的类型-->
<!--        association 处理多对一的映射关系
            property需要处理的多对映射关系属性名
            -->
        <association property="dept" javaType="Dept">
            <id property="did" column="did"></id>
            <result property="deptName" column="dept_name"></result>
        </association>
    </resultMap>
    <select id="getEmpAndDept" resultMap="empAndDeptResultTwo">
        select * from t_employ left join t_dept on t_employ.did = t_dept.did where t_employ.eid = #{eid}
    </select>

方式3:分布查询

先查询员工的信息

Emp getEmpAndDeptByStepOne(@Param("eid") int eid);

<!--     Emp getEmpAndDeptByStepOne(@Param("eid") int eid);-->
    <resultMap id="empAndDeptByStepResultMap" type="Emp">
        <id property="eid" column="eid"></id>
        <result property="empName" column="emp_name"></result>
        <result property="age" column="age"></result>
        <result property="sex" column="sex"></result>
        <result property="email" column="email"></result>
<!--        下面column写的是分步查询的条件
            sql语句的唯一标识,接口名.方法名等同于xml中的命名空间.select标签id
            得到这个方法的返回结果
            并且给dept这个属性赋值
-->
        <association property="dept"
                     select="mapper.DeptMapper.getEmpAndDeptByStepTwo"
                     column="did"
                     >
        </association>
    </resultMap>
    <select id="getEmpAndDeptByStepOne" resultMap="empAndDeptByStepResultMap">
        select * from t_employ where eid = #{eid}
    </select>

property:表示要给dept属性赋值

select:表示将这个方法的查询结果给dept赋值

column:将第一步查询的查询结果的did字段的值传递给select属性中的方法

再查询员工中部门的信息

Dept getEmpAndDeptByStepTwo(@Param("did") int did);

<!--    Dept getEmpAndDeptByStepTwo(@Param("did") int did);-->
    <select id="getEmpAndDeptByStepTwo" resultType="Dept">
        select * from t_dept where did = #{did}
    </select>

3.处理一对多关系查询

比如一个部门对应多个员工,查询一个部门信息并且显示属于该部门的所有员工信息

Step1:在Dept类中添加List

public class Dept {
    private int did;

    private String deptName;


    private List<Emp> empList;
}

Step2:

方式1:通过collection标签

将查询的两张表的结果直接给属性赋值

<!--    Dept getDeptAndEmp(@Param("did") int did);-->
    <resultMap id="deptAndEmpResultMap" type="Dept">
        <id property="did" column="did"></id>
        <result property="deptName" column="dept_name"></result>
<!--        ofType设置集合中元素的类型-->
        <collection property="empList" ofType="Emp">
            <id property="eid" column="eid"></id>
            <result property="empName" column="emp_name"></result>
            <result property="age" column="age"></result>
            <result property="sex" column="sex"></result>
            <result property="email" column="email"></result>
        </collection>
    </resultMap>
<select id="getDeptAndEmp" resultMap="deptAndEmpResultMap">
    select * from t_dept left join t_employ on t_dept.did = t_employ.did where t_dept.did = #{did}
</select>

方式2:分布查询

先查询部门信息

    //分布查询第一步:查询某个部门信息
    Dept getDeptAndEmpByStepone(@Param("did") int did);

<!--    Dept getDeptAndEmpByStepone();-->
    <resultMap id="deptAndEmpByStepResultMap" type="dept">
        <id property="did" column="did"></id>
        <result property="deptName" column="dept_name"></result>
<!--        select属性查询的结果为property赋值-->
        <collection property="empList"
                    select="mapper.EmpMapper.getDeptAndEmpByStepTwo"
                    column="did">
        </collection>
    </resultMap>
    <select id="getDeptAndEmpByStepone" resultMap="deptAndEmpByStepResultMap">
        select * from t_dept where did = #{did}
    </select>

将第一步查询结果的did传到select的方法中,再将查询结果赋值给属性empList

再查询员工的信息

    //分布查询2第二步:查询该部门的员工信息
    List<Emp> getDeptAndEmpByStepTwo(@Param("did") int did);

<!--    分布查询的第二步-->
<!--    Emp getDeptAndEmpByStepTwo(@Param("did") int did);-->
<select id="getDeptAndEmpByStepTwo" resultType="Emp">
    select * from t_employ where did = #{did}
</select>

4.延迟加载

 延迟加载

分布查询的好处就是延迟加载

​编辑

 这里说到的全局配置信息就是下划线转驼峰

关联的对象指的是分布查询的第二步,第三步等等   

aggressiveLoading开启后,会覆盖掉lazyLoadingEnabled,

要实现延迟加载的话

需要把aggressiveLoading设置为false,默认是false

把lazyLoadingEnabled设置为true,默认是false

​编辑

在设置了延迟加载之后,所有的查询都会延迟加载,如果有些功能不需要设置延迟加载,可以在association中设置fetchType来设置是立即加载还是延迟加载

当开启了延迟加载之后,通过fetchType使得延迟加载变得可控,如果没有开启延迟加载,那么这个属性就没有效果。

当设置了延迟加载,那么这个属性默认是lazy。

六.动态SQL

 Integer可以赋值null,int不能复制null

1.if标签

 ​编辑

<!--    if条件的使用-->
    <select id="getEmpByConditionOne" resultType="Emp">
        select * from t_employ where 1=1
        <!-- 判断,如果不满足条件就不会拼接到sql中
        如果第一个条件不成立的话,后面条件成了那么就相当于where后面直接跟了and,导致语法错误
        可以在where后面加一个1=1的条件,就可以拼接后面的and条件了
        -->
        <if test="empName != null and empName != ''">
            emp_name = #{empName}
        </if>
        <if test="age != null and age != ''">
            and age = #{age}
        </if>
        <if test="sex != null and sex != ''">
            and sex = #{sex}
        </if>
        <if test="email != null and email != ''">
            and email = #{email}
        </if>

    </select>

2.where标签

​编辑

<!--    where标签-->
<!--    List<Emp> getEmpByCondition(Emp emp);-->
    <select id="getEmpByConditionTwo" resultType="Emp">
        select * from t_employ
            <!--
            用了where标签的话,如果where标签中有if标签成立
            能够自动生成where关键字,还能把内容前多余的and去掉余的or也能去掉
            注意where标签不能将内容后面的and或or去掉,只能去掉内容前面的
            当所有的if标签都不成立,那么就不会生成关键字
            -->
        <where>
            <if test="empName != null and empName != ''">
                emp_name = #{empName}
            </if>
            <if test="age != null and age != ''">
                and age = #{age}
            </if>
            <if test="sex != null and sex != ''">
                and sex = #{sex}
            </if>
            <if test="email != null and email != ''">
                and email = #{email}
            </if>
        </where>
    </select>

3.trim标签

 ​编辑

 ​编辑

<!--trim标签-->
<!--    prefix/suffix:将trim标签中内容前面或后面添加指定内容
        suffixOverrides/prefixOverrides:将trim标签中内容前面或后面去掉指定内容
-->


    <select id="getEmpByConditionThree" resultType="Emp">
        select * from t_employ
<!--若标签中有内容时也就是有if标签生效时,会把最后面的指定内容去掉 and和or都生效
若标签中没有内容时也就是if标签都不生效时,trim标签没有任何效果,所以就不会加前缀
-->
            <trim prefix="where" suffix="" prefixOverrides="and|or" suffixOverrides="">

            <if test="empName != null and empName != ''">
                    emp_name = #{empName} and
            </if>
            <if test="age != null and age != ''">
                    and age = #{age} and
            </if>
            <if test="sex != null and sex != ''">
                    and sex = #{sex} and
            </if>
            <if test="email != null and email != ''">
                    and email = #{email}
            </if>
            </trim>
</select>

4.choose  when  otherwise标签

<!--    choose when  otherwise  相当于  if...else if... else,只要有一个成立其他的就不再判断了-->
<!--    List<Emp> getEmpByChooseWhenOtherwise(Emp emp);-->
<!--    如果when标签中有一个满足条件,就加上,如果when标签都不满足,则加上otherwise标签的条件
        when相当于 if或者else if  至少有一个
        otherwise相当于else,至多有一个
-->
    <select id="getEmpByChooseWhenOtherwise" resultType="Emp">
        select * from t_employ
        <where>
            <choose>
                <when test="empName != null and empName != ''">
                    emp_name = #{empName}
                </when>
                <when test="age != null and age != ''">
                    age = #{age}
                </when>
                <when test="sex != null and sex != ''">
                    sex = #{sex}
                </when>
                <when test="email != null and email != ''">
                    email = #{email}
                </when>
                <otherwise>
                    did = 1
                </otherwise>
            </choose>
        </where>
    </select>

 ​编辑

 第一个条件成立,剩下的条件就不判断了

5.foreach标签

<!--注意分隔符前面和后面默认会加上空格,所以用or作连接时不用担心空格的问题-->
    <delete id="deleteMoreByArray">
        delete from t_employ where
        <foreach collection="eids" item="eid" separator="or">
            eid = #{eid}
        </foreach>
    </delete>
<!--    int deleteMoreByArray(Integer[] eids);-->
    <delete id="deleteMoreByArrayOne">
        delete from t_employ where eid in
        <!--item表示数组中的每个元素,separator设置循环体中每一次循环的分隔符
        open表示循环的内容以什么开始
        close表示循环的内容以什么结束
        (
        <foreach collection="eids" item="eid" separator="," close="" open="">
            #{eid}
        </foreach>
        )

        -->
        <foreach collection="eids" item="eid" separator="," close=")" open="(">
            #{eid}
        </foreach>
    </delete>

//通过数组实现批量删除
    int deleteMoreByArray(@Param("eids") Integer[] eids);

    //通过list集合实现批量添加
    int insertMoreByList(@Param("emps") List<Emp> emps);

实现批量添加和批量删除

通过数组实现批量删除,通过集合实现批量添加。

6.sql标签定义字段名

<!--    避免在select后面写太多的字段名,通过sql标签定义常用的字段名通过include引用sql片段-->
    <sql id="empColums">eid,emp_name,age,sex,email</sql>
    <select id="getEmpByCondition" resultType="Emp">
    select <include refid="empColums"></include> from t_employ
    </select>

七.缓存

 缓存:会把查询出来的数据进行记录,下次查询相同数据的时候就会从缓存中去拿,不会重新访问数据库了。缓存只对查询功能有效。

1.一级缓存

​编辑

 通过同一个SqlSession查询出来的数据会被缓存,再次通过这个SqlSession查询数据就会从缓存中取。

​编辑

 ​编辑

观察执行的sql,第二个语句没有执行sql说明是从缓存中拿到的数据

​编辑

同一个sqlSession 获取的mapper

​编辑

不同sqlSession获取的mapper   

一级缓存的范围是同一个SqlSession

​编辑

两个查询之间有一次插入操作,则一级缓存失效。

​编辑

清空缓存

2.二级缓存

​编辑

 关闭或提交之后:sqlSession有两个方法:commit和close方法

在没有提交或者关闭sqlSession时,查询的数据会保存到一级缓存中,如果开启了二级缓存,那么提交或关闭sqlSession后,查询的数据会保存到二级缓存中

实现序列化接口

​编辑

 手动清空缓存只对一级缓存有效,因为clearCache方法只在SqlSession中

二级缓存的相关配置

在xml中设置cache标签的属性

​编辑

缓存是缓存到内存中的

缓存回收策略是为了节省内存

刷新间隔:二级缓存多长时间刷新一次,clearCache只会清空一级缓存

缓存仅仅调用语句时刷新指的是调用增删改的语句时。

读写缓存时,返回给用户缓存对象的副本,对这个副本进行修改不会影响缓存中的数据

MyBatis缓存查询的顺序

​编辑

 整合第三方缓存EHCache

由其他的技术代替二级缓存

​编辑

 日志门面 相当于里面提供的接口

门面的一个具体实现就是接口的一个具体实现

​编辑

​编辑

​编辑

​编辑

​编辑

​编辑

八.MyBatis的逆向工程

​编辑

逆向工程有两个版本

清新简洁版 只有增删改查五个方法

奢华尊享版中,带有example的方法,都是根据条件的执行操作 

1.启动逆向工程

Step1:配置pom.xml文件

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>MyBatis_MBG</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>16</maven.compiler.source>
        <maven.compiler.target>16</maven.compiler.target>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.7</version>
        </dependency>
        <!--        日志信息-->
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.12</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>RELEASE</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter</artifactId>
            <version>RELEASE</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.27</version>
        </dependency>
        <dependency>
            <groupId>com.github.pagehelper</groupId>
            <artifactId>pagehelper</artifactId>
            <version>5.2.0</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.mybatis.generator</groupId>
                <artifactId>mybatis-generator-maven-plugin</artifactId>
                <version>1.3.0</version>
<!--插件的依赖-->
                <dependencies>
<!--                    逆向工程的核心依赖-->
                    <dependency>
                        <groupId>org.mybatis.generator</groupId>
                        <artifactId>mybatis-generator-core</artifactId>
                        <version>1.3.2</version>
                    </dependency>
                    <dependency>
<!--                        数据库连接池-->
                        <groupId>com.mchange</groupId>
                        <artifactId>c3p0</artifactId>
                        <version>0.9.2</version>
                    </dependency>
<!--                    MySQL驱动-->
                    <dependency>
                        <groupId>mysql</groupId>
                        <artifactId>mysql-connector-java</artifactId>
                        <version>8.0.27</version>
                    </dependency>
                </dependencies>
            </plugin>
        </plugins>
    </build>
</project>

Step2:MyBatis核心配置文件配置

<?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>
    <properties resource="jdbc.properties"/>

    <typeAliases>
        <package name="pojo"/>
    </typeAliases>

    <plugins>
        <plugin interceptor="com.github.pagehelper.PageInterceptor"></plugin>
    </plugins>


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

    <mappers>
        <package name="mapper"/>
    </mappers>
</configuration>

Step3:逆向工程配置文件,名字必须为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>

<!--    targetRuntime:执行生成的逆向工程的版本
                        MyBatis3Simple:生成基本的CRUD(清新简洁版)
                        MyBatis3:生成带条件的CRUD(奢华尊享版 )-->
    <context id="DB2Tables" targetRuntime="MyBatis3">
        <!--数据库连接的信息:驱动类、连接地址、用户名、密码 -->
        <jdbcConnection driverClass="com.mysql.cj.jdbc.Driver"
                        connectionURL="jdbc:mysql://localhost:3306/mybatis"
                        userId="root"
                        password="niit" />
<!--        Javabean生成策略-->
        <javaModelGenerator targetPackage="pojo"  targetProject=".\src\main\java" >
<!--            是否能够使用此包,如果写true,那么targetPackage中每个点都对应一层包 如com.mybatis.pojo
                是false的话就认为com.mybatis.pojo是`一个`目录-->
            <property name="enableSubPackages" value="true"/>
<!--            是否去掉开头和结尾的空格,字段名前后有空格的话会自动去掉-->
            <property name="trimStrings" value="true"/>
        </javaModelGenerator>
<!--SQL映射文件生成策略-->
        <sqlMapGenerator targetPackage="mapper" targetProject=".\src\main\resources">
            <property name="enableSubPackages" value="true"/>
        </sqlMapGenerator>
<!--Mapper接口的生成策略-->
        <javaClientGenerator targetPackage="mapper" targetProject=".\src\main\java" type="XMLMAPPER">
            <property name="enableSubPackages" value="true"/>
        </javaClientGenerator>


<!--       逆向分析表-->
        <table tableName="t_employ" domainObjectName="Emp" />
        <table tableName="t_dept" domainObjectName="Dept"/>
    </context>
</generatorConfiguration>

Step4:在数据库中创建对应的表

Step5:双击插件创建逆向工程的mapper,pojo

​编辑

九.其他功能

1.获取自增的主键

useGeneratedKeys:设置当前标签中的sql使用了自增的id

keyProperty:将自增的主键的值返回,因为insert delete等操作的返回值只能是受影响

的行数,所以要想返回主键的值,只能将这个主键的值赋值到对象的某个属性中。

因为传进来的是一个对象。

void insertUser(User user);

<!--
keyProperty 表示把自动递增的主键放到对象的id属性中

-->
<insert id="insertUser" useGeneratedKeys="true" keyProperty="id">
    insert into t_user values(null,#{username},#{password},#{age},#{sex},#{email})
</insert>

@Test
public void testWay5() throws IOException {
    SqlSession sqlSession = SqlSessionUtils.getSqlSession();
    ParameterMapper mapper = sqlSession.getMapper(ParameterMapper.class);
    User user = mapper.checkLoginByParam("admin", "123");
    System.out.println(user);
}

2. 分页插件

​编辑

​编辑

  ​编辑

PageInfo{pageNum=1, pageSize=1, size=1, startRow=1, endRow=1, total=5, pages=5, list=Page{count=true, pageNum=1, pageSize=1, startRow=0, endRow=1, total=5, pages=5, reasonable=false, pageSizeZero=false}[Emp{eid=1, empName='张三', age=16, sex='男', email='123@qq.com', did=1}], prePage=0, nextPage=2, isFirstPage=true, isLastPage=false, hasPreviousPage=false, hasNextPage=true, navigatePages=5, navigateFirstPage=1, navigateLastPage=5, navigatepageNums=[1, 2, 3, 4, 5]}

pageSize:指定每页显示的条数

size:当前页显示的条数,如最后一页可能显示不到指定数量

prePage:上一页

nextPage:下一页

isFirstPage:是否是上一页

isLastPage:是否是下一页

hasPreviousPage:是否有上一页

hasNextPage:是否有下一页

navigatePages:导航分页

navigatepageNums:根据navigatePages计算出显示的页数,将正在显示的页数放到正中间。