MyBatis

工具类

public class SqlSessionFactoryUtil {

    private static InputStream is;
    private static SqlSessionFactory sqlSessionFactory;

    static {
        try {
        	//读取核心配置文件
            is = Resources.getResourceAsStream("mybatis-config.xml");
            //获取SqlSessionFactoryBuilder对象 SqlSessionFactory对象 
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
	//获取sqlSession对象/参数是否默认自动提交事务 默认不自动提交 设置为true
    public static SqlSession getSqlSession(){
        return sqlSessionFactory.openSession(true);
    }
	//关闭流
    public static void close(){
        try {
            is.close();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

作用域和生命周期:

1、SqlSessionFactoryBuilder:这个类可以被实例化、使用和丢弃,一旦创建了SqlSessionFactory,就不再需要它了
2、SqlSessionFactory:一旦被创建就应该在运行期间一直存在,没有理由丢弃它或重新创建另一个实例,多次重建 SqlSessionFactory 被视为一种代码“坏习惯”。
3、SqlSession:每个线程都应该有自己的 SqlSession 实例,且 SqlSession 的实例是线程不安全的,不能被共享

获取参数的两种方式:#{}和${}

#{}的本质是:占位符赋值;${}本质是字符串拼接。
所以我们尽可能的使用#{},而不是${}
当必须使用${}时,注意需要加上单引号'${}'

特殊情况

  1. 模糊查询
    解决方案:1、使用'%${xxx}%。2、使用concat函数,例如:concat('%',#{xxx},'%')。3、使用字符串拼接,例如:"%"#{xxx}"%"
  2. 动态设置表名
    只能用${xxx}

字段名和属性名不一致问题

  1. 设置别名
select eid,emp_name AS empName,age,sex,email from t_emp;
  1. 通过全局配置
    此时只能解决,例如emp_nameempName形式的自动映射
<setting name="mapUnderscoreToCamelCase" value="true"/>
  1. 通过resultMap自定义映射关系 解决字段名和属性值不一致问题
    此时所有的属性字段都需要重写
	<resultMap id="empResultMap" 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>
    </resultMap>
    <select id="selectEmps" resultMap="empResultMap">
         select * from t_emp;
    </select>

mybatis获取参数值的各种情况

1、mapper接口方法的参数为单个字面量时

  • 此时用#{}或者${}都可以,但是 ${}需要加单引号

2、mapper接口的方法参数有多个时
此时mybatis会自动将这些参数放入一个map中,以两种方式存储

  • 以arg0、arg1…为键,以参数为值
  • 以para1、param2…为键,以参数为值

3、mapper接口方法的参数有多个时,我们可以手动将参数存入map中,传入map参数

<!--mapper接口-->
User selectUserByMap(Map<String,String> map);
<!--mapper.xml-->
<select id="selectUserByMap" resultType="user">
   select * from t_user where username = #{username} and password = #{password};
</select>
<!--测试类-->
Map<String,String> map = new HashMap<>();
map.put("username","zs");
map.put("password","1234");

4、mapper接口方法参数是一个实体类型

  • 直接通过属性使用#{属性}...

5、在mapper接口的方法中使用@Param("xxx")来命名参数

User selectUserByParam(@Param(value ="username"));

使用:

  1. #{xxx}为键,以参数为值
  2. #{param1}#{param2}为键,以参数为值

综上,只有(4/5)两种情况

查询结果

查询结果为一条

可以用实体类对象、List集合、map接收

查询结果为多条

可以用List集合、map[]、List<Map<String,Object>>接收以及map和@MapKey(“主键”)结合使用,例如:

@MapKey("id")
Map<Integer, Object> selectUsersToMap(@Param("username")String username);

单行单列

int…

多对一或者一对一映射关系

通过级联属性赋值解决多对一问题

<resultMap id="empAndDeptResultMapOne" type="Emp">
        <id property="eid" column="eid"></id>
        <result property="empName" column="emp_name"></result>
        <result property="dept.did" column="did"></result>
        <result property="dept.deptName" column="dept_name">
</resultMap>

通过association解决多对一问题

association: 处理多对一的映射关系
property: 需要处理多对的映射关系的属性名
javaType: 该属性的类型

<resultMap id="empAndDeptResultMapTwo" type="Emp">
        <id property="eid" column="eid"></id>
        <result property="empName" column="emp_name"></result>
        <association property="dept" javaType="Dept">
            <id property="did" column="did"></id>
            <result property="deptName" column="dept_name">
        </association>
</resultMap>
    
<select id="getEmpAndDept" resultMap="empAndDeptResultMapTwo">
        select * from t_emp left join t_dept on t_emp.did = t_dept.did where eid = #{eid}
</select>

通过分步查询解决多对一问题(重点

select: 设置分步查询的sql的唯一标识(mapper接口的全类名.方法名)
column: 设置分步查询的条件
fetchType: 当开启了全局的延迟加载之后,可通过fetchType属性手动控制延迟加载的效果
fetchType=“eager|lazy”: 若没有开启全局的延迟加载,两个属性值都表示立即加载

 <resultMap id="empAndDeptByStepResultMap" type="Emp">
        <id property="eid" column="eid"></id>
        <result property="empName" column="emp_name"></result>
        <association property="dept"
             		 select="com.herdgod.mapper.DeptMapper.getEmpAndDeptByStepTwo"
                     column="did"
                     fetchType="eager"></association>
</resultMap>
    
<select id="getEmpAndDeptByStepOne" resultMap="empAndDeptByStepResultMap">
        select * from t_emp where eid = #{eid}
</select>

一对多映射关系

通过collection解决一对多问题

collection: 处理一对多的映射关系
ofType: 表示该属性所对应集合中存储数据的类型

<resultMap id="deptAndEmpResultMap" type="Dept">
        <id property="did" column="did"></id>
        <result property="deptName" column="dept_name"></result>
        <collection property="emps" ofType="Emp">
            <id property="eid" column="eid"></id>
            <result property="empName" column="emp_name"></result>
        </collection>
</resultMap>
    
<select id="getDeptAndEmp" resultMap="deptAndEmpResultMap">
        select * from t_dept left join t_emp on t_dept.did = t_emp.did where t_dept.did = #{did}
</select>

通过分步查询处理一对多问题(重点)

select: 设置分步查询的sql的唯一标识(mapper接口的全类名.方法名)
column: 设置分步查询的条件
fetchType: 当开启了全局的延迟加载之后,可通过fetchType属性手动控制延迟加载的效果
fetchType=“eager|lazy”: 若没有开启全局的延迟加载,两个属性值都表示立即加载

<resultMap id="deptAndEmpByStep" type="Dept">
        <id property="did" column="did"></id>
        <result property="deptName" column="dept_name"></result>
        <collection property="emps"
                    select="com.herdgod.mapper.EmpMapper.getDeptAndEmpByStepTwo"
                    column="did"></collection>
</resultMap>
    
<select id="getDeptAndEmpByStepOne" resultMap="deptAndEmpByStep">
        select * from t_dept where did = #{did}
</select>

多对多映射关系

1、通过创建一个新的类解决,不可取,这里不讲述(不推荐)
2、通过分步查询方式解决多对多映射关系(重点

场景:一个用户有多个角色,一个角色可以有多个用户
业务:查询所有用户的所有角色信息
用户实体


@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {

    private Integer id;
    private String username;
    private Date birthday;
    private String sex;
    private String address;
    //角色
    private List<Role> roles;
}

角色实体

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Role {

    private Integer rid;
    private String roleName;
    private String roleDesc;
}

用户接口

public interface UserMapper {
    // 查询全部
    List<User> selectAll();
}

角色接口

public interface RoleMapper {
    /**
     * 通过rid查询
     */
    List<Role> findAllByRid(@Param("rid") Integer rid);
}

用户映射文件

<mapper namespace="com.herd.mapper.UserMapper">

<!--List<User> selectAll();  fetchType="lazy"-->
    <resultMap id="manyMap" type="user">
        <id property="id" column="id"></id>
        <result property="username" column="username"></result>
        <result property="birthday" column="birthday"></result>
        <result property="sex" column="sex"></result>
        <result property="address" column="address"></result>
        <collection property="roles"
                    select="com.herd.mapper.RoleMapper.findAllByRid"
                    column="id"></collection>
    </resultMap>
    <select id="selectAll" resultMap="manyMap">
        select * from user
    </select>
</mapper>

角色映射文件

<mapper namespace="com.herd.mapper.RoleMapper">

<!--List<Role> findAllByRid(@Param("rid") Integer rid);-->
    <resultMap id="map01" type="role">
        <id column="rid" property="rid"></id>
        <result property="roleName" column="role_name"></result>
        <result property="roleDesc" column="role_desc"></result>
    </resultMap>
    <select id="findAllByRid" resultMap="map01" parameterType="int">
        select * from role WHERE rid in (
        SELECT rid from user_role where UID = #{rid}
        )
    </select>
</mapper>

测试:

 @Test
    public void testManyToMany(){
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        List<User> users = mapper.selectAll();
        for (User user : users) {
            System.out.println(user);
            //System.out.println(user.getUsername());
        }
    }

结果:
在这里插入图片描述
如果我们只想插入所有的用户信息,不需要相关的角色信息时,我们可以使用延迟加载
延迟加载:就是在需要用到数据的时候才进行加载,不需要用到数据时就不加载数据
延迟加载开启
在mybatis核心配置文件中设置

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

MyBatis插入之批处理

需求:批量新增一批数据

解决方式一:将批量新增的数据 放在List集合里面,遍历集合循环新增数据(不推荐)
	@Test
    public void test01(){
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        List<User> users = new ArrayList<>();
        User user1 = new User("测试数据1","123",19,"男","123@qq.com");
        User user2 = new User("测试数据2","123",19,"男","123@qq.com");
        users.add(user2);
        users.add(user1);
        for (User user : users) {
            mapper.insertUsers(user);
        }
    }

运行结果
在这里插入图片描述
由于insert语句多次执行,造成执行效率很低

解决方式二:开启批处理,使用批处理的方式进行数据的新增

开启批处理的方式有两种:
需要注意的是:这时需要手动提交事务!!
第一种:在mybatis的核心配置文件上开启

	<settings>
        <setting name="defaultExecutorType" value="BATCH"/>
    </settings>

第二种:在创建SqlSession对象的时候,开启批处理

sqlSession = sessionFactory.openSession(ExecutorType.BATCH,false);

再次运行
在这里插入图片描述

添加后获取自增的主键

方式一
keyProperty: 实体类中的属性,对应数据库表中的自增主键
keyColumn:数据库中表的自增的主键字段

    <insert id="insertUser" parameterType="user">
        <selectKey keyProperty="id" keyColumn="id" resultType="int">
            select last_insert_id()
        </selectKey>
        insert into t_user values(null,#{username},#{password},#{age},#{sex},#{email})
    </insert>

方式二

useGeneratedKeys: 设置当前标签中的sql使用了自增的主键
keyProperty:将自增的主键的值 赋值给传输到实体类型中的属性

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

动态SQL

动态sql

缓存

Mybatis中的缓存机制,一般无法做到缓存同步,即数据库中的数据改变,无法察觉到

一级缓存

基于PerpetualCache的HashMap本地缓存,其存储作用域为Session级别,当Session进行flush或者close后,该Session中的所有Cache就将清空,默认打开一级缓存。

一级缓存失效的四种情况:

  1. 不同的SqlSession
  2. 同一个SqlSession,查询条件不同
  3. 手动清理了缓存,clearCache
  4. 同一个SqlSession两次查询条件相同,但是在中间执行了增删改语句
二级缓存

基于namespace和mapper的作用域起作用的,不是依赖于SqlSession,默认也是采用PerpetualCache,HashMap存储,需要单独开启。
步骤:

  1. 核心配置文件中
<settings>
	<!-- 开启二级缓存的支持 -->
	<setting name="cacheEnabled" value="true"/>
</settings>
  1. mapper映射文件中
<!-- 开启二级缓存的支持 -->
<cache></cache>
  1. 二级缓存必须在SqlSession关闭或者提交之后有效
  2. 查询的数据所转换的实体类型必须实现序列化的接口

二级缓存失效的情况:

  • 两次查询之间执行了任意的增删改,会使一级和二级缓存同时失效

二级缓存的相关配置:
在mapper配置文件中添加的cache标签可以设置一些属性:

  • eviction属性:缓存回收策略
    LRU:最近最少使用,溢出最长时间不被使用的对象(默认)
    FIFO:先进先出,按对象进入缓存的顺序来移除它们
    SOFT:软引用,移除基于垃圾回收器状态和软引用规则的对象
    WEAK:弱引用,更积极地移除基于垃圾回收器状态和弱引用规则的对象
  • flushInterval属性:刷新间隔,单位毫秒
    默认情况是不设置,也就是没有刷新时间间隔,缓存仅仅调用语句时刷新
  • size属性:引用数目,正整数
    代表缓存最多可以存储多少个对象,太大容易导致内存溢出
  • readOnly属性:只读,true/false
    true:只读缓存,会给所有调用者返回缓存对象的相同实例。因此这些对象不能被修改,提供了很重要的性能优势
    false:读写缓存,会返回缓存对象的拷贝(通过序列化)。效率低一些,但是安全,默认false。

缓存查询顺序

  • 先查询二级缓存,因为二级缓存中可能会有其他程序已经查出来的数据,可以拿来直接使用
  • 如果二级缓存没有命中,再查询一级缓存
  • 如果一级缓存也没有命中,则查询数据库
  • SqlSession关闭后,一级缓存中的数据会写入二级缓存

整合第三方缓存:Ehcache(了解)

逆向工程

  1. 引入依赖和插件
<dependencies>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
        </dependency>
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
    </dependencies>
<build>
        <plugins>
            <plugin>
                <groupId>org.mybatis.generator</groupId>
                <artifactId>mybatis-generator-maven-plugin</artifactId>
                <version>1.3.7</version>
                <dependencies>
                    <!-- 逆向工程的核心依赖 -->
                    <dependency>
                        <groupId>org.mybatis.generator</groupId>
                        <artifactId>mybatis-generator-core</artifactId>
                        <version>1.3.7</version>
                    </dependency>
                    <!-- 数据库连接池 -->
                    <dependency>
                        <groupId>com.alibaba</groupId>
                        <artifactId>druid</artifactId>
                        <version>1.0.9</version>
                    </dependency>
<!--                    <dependency>-->
<!--                        <groupId>com.mchange</groupId>-->
<!--                        <artifactId>c3p0</artifactId>-->
<!--                        <version>0.9.5.2</version>-->
<!--                    </dependency>-->
                    <!-- MySQL驱动 -->
                    <dependency>
                        <groupId>mysql</groupId>
                        <artifactId>mysql-connector-java</artifactId>
                        <version>8.0.31</version>
                    </dependency>
                </dependencies>
            </plugin>
        </plugins>
    </build>
  1. 创建mybatis的核心配置文件:略
  2. 创建逆向工程的配置文件
    注意:逆向工程的文件名必须是generatorConfig.xml
    简洁(丐)版:targetRuntime="MyBatis3Simple
    高配版:targetRuntime="MyBatis3
    区别
    简洁版:只有简单的crud,高配版:除了简单的crud还有模糊查找、in()等
    都只能生成单表操作,不能处理多表关系
<?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="MyBatis3Simple">
        <!-- 数据库的连接信息 -->
        <jdbcConnection driverClass="com.mysql.cj.jdbc.Driver"
                        connectionURL="jdbc:mysql://localhost:3306/demo"
                        userId="root"
                        password="wr990916">
        </jdbcConnection>

        <!-- javaBean的生成策略-->
        <javaModelGenerator targetPackage="com.herd.pojo"
                            targetProject=".\src\main\java">
            <!--
            是否生成子包。如果为true com.herd.pojo生成的保姆那个带有层级
            目录
            false com.herd.pojo就是一个包名
            -->
            <property name="enableSubPackages" value="true" />
            <!--
            通过数据表字段生成pojo。如果字段名称带空格,会去掉空格
            -->
            <property name="trimStrings" value="true" />
        </javaModelGenerator>

        <!-- SQL映射文件的生成策略 -->
        <sqlMapGenerator targetPackage="com.herd.mapper"
                         targetProject=".\src\main\resources">
            <property name="enableSubPackages" value="true" />
        </sqlMapGenerator>

        <!-- Mapper接口的生成策略 -->
        <javaClientGenerator type="XMLMAPPER"
                             targetPackage="com.herd.mapper"
                             targetProject=".\src\main\java">
            <property name="enableSubPackages" value="true" />
        </javaClientGenerator>

        <!-- 逆向分析的表 -->
        <!-- tableName设置为*号,可以对应所有表,此时不写
                domainObjectName -->
        <!-- domainObjectName属性指定生成出来的实体类的类名 -->
        <table tableName="t_user" domainObjectName="User"/>
    </context>
</generatorConfiguration>

生成后的项目结构:
在这里插入图片描述

分页插件

引入依赖

<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper</artifactId>
    <version>5.3.1</version>
 </dependency>

在MyBatis核心配置文件中配置插件

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

查询相关常用数据:

  • pageNum:当前页的页码
  • pageSize:每页显示的条数
  • size:当前页显示的真实条数
  • total:总记录数
  • pages:总页码数
  • prePage:上一页的页码
  • nextPage:下一页的页码
  • isFirstPage/isLastPage:是否为第一页/最后一页
  • hasPreviousPage/hasNextPage:是否存在上一页/下一页
  • navigatePages:导航分页的页码数
  • navigatepageNums:导航分页的页码,[1,2,3,4,5]

MyBatis执行流程

1.加载MyBatis配置文件:mybatis-config.xml加载运行环境和映射文件
2.构造会话工厂SqlSession
3.会话工厂创建SqlSession对象(包含了执行SQL语句的所有方法)
4.操作数据库的接口,Executor执行器,同时负责查询缓存的维护
5.Executor接口的执行方法中有一个MapperStatement类型的参数,封装了映射信息
6.输入参数映射
7.输出结果映射

延迟加载的底层原理

1.使用CGLIB创建目标对象的代理对象
2.当调用目标方法时,进入拦截器invoke方法,发现目标方法是null值,执行sql查询
3.获取数据以后,调用set方法设置属性值,再继续查询目标方法,就有值了

MyBatis核心配置文件

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

<!--设置别名-->
    <typeAliases>
        <!--默认时类名,不区分大小写-->
        <typeAlias type="com.herdgod.pojo.User" alias="user"></typeAlias>
        <!--常用: 表示把pojo包下的所有都设置别名为 类名 小写-->
        <!--<package name="com.herdgod.pojo"/>-->
    </typeAliases>

<!--environments: 配置mybatis的环境-->
    <environments default="development">
        <!--environment: 配置某个具体的环境
            属性: id:表示连接数据库的环境唯一标识,不能重复-->
        <environment id="development">
            <!--transactionManager:设置事务的管理方式
                属性:JDBC|MANAGED
                    JDBC:表示当前环境中,执行sql时,使用的是JDBC中原生的事务管理方式,事务的提交回滚需要手动控制
                    MANAGED:被管理,例如Spring,意思就是没什么卵用-->
            <transactionManager type="JDBC"/>
            <!--dataSource:配置数据源
                属性:
                    type: 设置数据源的类型
                        POOLED: 表示使用数据库连接池缓存数据库连接
                        UNPOOLED:表示不适用数据库连接池
                        JNDI: 表示使用上下文中的数据源-->
            <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>
        <mapper resource="com/herdgod/mapper/UserMapper.xml"></mapper>
        <!--<mapper class="com.herdgod.mapper.UserMapper"></mapper> 不常用-->
        <!--以包为单位引入映射文件
                要求:
                1、mapper接口所在的包要和映射文件所在的包一致
                2、mapper接口要和映射文件的名字一致
                创建多层包时需要用/分隔 com/herdgod/mapper -->
        <!--<package name="com.herdgod.mapper"/>-->
    </mappers>
</configuration>

映射文件

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--mybatis面向接口编程的两个一致
1、映射文件的namespace和mapper接口的全类名一致
2、映射文件中SQL语句的id和mapper接口中的方法名一致
-->
<mapper namespace=".....">

</mapper>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值