Mybatis,MybatisPlus

1 Mybatis

1.1 mybatis

  • 原始SQL编程复杂,依赖数据库,移植性差,功能欠缺

  • 支持定制化SQL、存储过程和高级映射的数据持久层框架,消除JDBC样板代码;二级缓存机制不佳

  • 关键类和执行过程/生命周期:

    1. 编写并加载全局配置文件mybatis-config.xml:

      <?xml version="1.0" encoding="utf-8" ?>
      <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
      <configuration>
          <!-- 配置全局属性 -->
          <typeAliases alias="类别名" type="类"></typeAliases>
          <!-- 当与Spring结合时选择使用Spring的配置 -->
          <enviroments>数据源等</enviroments>
          
          <settings>
              <!-- 使用JDBC的getGeneratedKeys获取数据库自增主键策略;插入bean时,数据库id自增而bean的id值为null,查询时不为null;需要指明主键和启用 -->
              <setting name="useGeneratedKeys" value="true"/>
              <!-- 使用列标签替换列别名 -->
              <setting name="useColumnLabel" value="true"></setting>
              <!-- 开启驼峰命名转换 -->
              <!-- 查询数据时,若某个字段在数据库有值,而查询出来为null,很可能就是因为字段名和属性名对应不上 -->
              <setting name="mapUnderscoreToCamelCase" value="true" />
          </settings>
          
          <!-- 注册mappper类 -->
          <mappers></mappers>
      </configuration>
      // 读取配置文件
      InputStream configFile = new FileInputStream(filePath);
      InputStream configFile = Resource.getResourceAsStream(filePath);
    2. SqlSessionFactoryBuilder:读取配置文件,使用工厂创建对象;属于method/request级别,即方法返回后消失;作用是build()中解析全局配置文件;解析完会产生全局单例configuration;该bean是管理配置文件信息的容器

    3. SqlSessionFactory,SqlSession:使用工厂创建对话和核心对象;SqlSessionFactory为Application级别;SqlSession为request级别;作用是创建会话;openSession()获取会话;过程中创建了事务工厂、执行器,最后返回DefaultSqlSession核心对象,但非线程安全,整合Spring后要用其代理类;封装操作数据库的方法

       // 调用SqlsessionFactoryBuilder对象的build(inputStream)创建XMLConfigBuilder对象(inputStream读取配置文件作输入流);Builder设计模式;新对象调用parse()解析xml文件(转为document对象,解析DOM节点Node即标签<...>,封装),得到Configuration对象
      SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(configFile);
      // 新对象创建DefaultSessionFactory/SqlSessionFactory对象;与Spring整合后由SqlSessionTemplate生成
      SqlSession sqlSession = sqlSessionFactory.openSession(true);
      // SqlSession:默认一级缓存:session级别的缓存,即在同一个session中查询,再做相同查询时从缓存中获取数据,sql仅一条;不同session中执行相同查询,则各自查询数据库即二级缓存;查询之间有增删改操作则不视为相同查询,即增删改会自动flush缓存
  • Mapper,MapperProxy:调用动态代理;属于request级别;作用是调用MapperProxy.invoke()动态代理
  • 核心映射器配置:

    1. 解决问题:

      1. 传统JDBC底层没有用连接池,数据库操作会频繁创建和关闭,消耗资源

      2. 修改SQL繁琐且需要整体编译,不利于维护

    2. ORM用于实现不同类型系统的数据之间的切换;如bean中为id类型Long,切换到数据库为bigint

1.2 Mapper与动态SQL

  • mybatis读取mapper文件的四种方式:< mappers>< mapper resource/url/class/name="userMappper.xml">< /mapper>< /mappers>
    <!-- name为绑定到上下文的变量名,value为OGNL表达式 -->
    <if test="userName != null and userName != ''">
    	<bind name="nameLike" value="'%' + userName + '%'"/>
    	and user_name like #{nameLike} 
    </if>
    
  • 编写DaoMapper接口及对应的.xml:

    <!-- namespace指定扫描对应包下的Bean -->
    <mapper namespace="命名空间,类对象路径">
        <!-- 封装表名,后面所有需要表名的地方都引用tableName,如select from <include refid="tableName" /> -->
        <sql id="tableName">表名</sql>
        <!-- 封装有id和无id的所有字段共后面引用,如select <include refid="all_columns" -->
        <sql id = "all_columns">
            id,
            name,
            ...
        </sql>
        <!-- 封装参数映射值 -->
        <sql id="params">
            #{id},
            #{name},
            ...
        </sql>
        <!-- 封装返回结果,resultMap自定义映射结果(高级映射);当bean属性名与表字段名不一致时,也可用此建立映射关系,属性上使用@Column --> 
        <!-- 注意查询返回List<Map>类型数据时,返回结果要写为resultType="map"才能映射到 -->
        <resultMap id="beanResult" type="bean路径">
        	<result property="bean属性名" column="表字段名" jdbcType="字段类型"></result>
            ...
        </resultMap>
        
        <!-- 执行<insert><update><select><delete>等statement -->
        
        <!-- 查;注意查询的动态SQL语句中没有逗号 -->
        <select id="对应dao层中方法名" parameterType="传入参数的类型;如果值为int或Integer,表示Integer,即int是Integer的别名;而_int表示int" resultType="返回结果类型,如果是resultMap,则可能='beanResult'">
            select <include refid="all_columns"/>
            from <include refid="tableName"/>
            left join 表名 
            <if test="userName!=null and userName!=''">
                on 表.user_name = 表.user_name
            </if>
            where 1=1
            <if test="id!=null">
                and id = #{id}
            </if>
            <!-- 参数条件查询;判断参数是否为空;前位为表字段名,后为参数名/属性名 -->
            <if test="userName!=null and userName!=''">
            	and user_name = #{userName}
            </if>
            <!-- 当参数类型为时间类型 -->
            <if test="startTime!=null and startTime!='' and endTime!=null and endTime!=''">
                and time >= #{startTime} and time &lt;=(小于符号在xml中被解析为左键括号,可写为&lt;) #{endTime}
                <!-- 有时候需要使用$直接获取值,如 -->
                and time > ${startTime}
            </if>
            
            <!-- 模糊查询 -->
            <if test="userName!=null and userName!=''">
                and user_name like '${userNmame}%'
                and user_name like CONCAT('%', #{userName}, '%')
            </if>
            <!-- 传入一个参数,做所有字段的条件匹配 -->
           	<if test="keyWord!=null and keyWord!=''">
                and user_name like concat(#{keyWord}, %)
            </if>
            <if test="keyWord!=null and keyWord!=''">
                and user_age like concat(#{keyWord}, %)
            </if>
            ...
            order by time desc
        </select>
        
        <!-- 增 -->
        <insert id="add" parameterType="beanName">
            insert into <include refid="tableName"/>
            (<include refid="all_columns"/>)
            values
            (<include refid="all_columns")
        </insert>
        <!-- 批量增 -->
        <insert id="add" parameterType="list">
            insert into <include refid="tableName"/>
            (<include refid="all_columns"/>)
            values
             <foreach collection="beanList" item="item" open="(" separator="," close=")">
                (<include refid="all_columns"/>)
            </foreach>
        </insert>
            
        <!-- 删 -->
        <delete id="delete" parameterType="String">
            delete from <include refid="tableName"/>
            where id = #{id}
            <!-- 批量删除操作;parameterType="String[]";foreach用来循环collection -->
            <!-- 
    			collection:指定传入的需要循环的参数名,可取array,list,map
    			item:为循环中每个元素指定别名;index:下标
    			open:循环开始;close:循环结束;separator:元素之间的分隔符
     		-->
            where id in
            <foreach collection="ids" item="id" open="(" separator="," close=")">
                #{id}
            </foreach>
        </delete>
            
        <!-- 改;注意修改的动态SQL语句中有逗号,且最后一条set没有逗号 -->
        <update id="update" parameterType="beanName">
            update <include refid="tableName"/>
            <set>
                <if test="userName!=null and userName!=''">
                    user_name = #{userName},
                </if>
                ...
            </set>
            where id = #{id}
        </update>
    </mapper>
  • Bean中不再使用关联对象的属性即外键,而直接将关联对象做为属性

    • < association property="关联对象变量名" column="查询关联对象的列名一般为id" javaType="关联对象" select="关联对象对应mapper.xml中对应查询sql语句的id全名">:一对一/多对一,对应java的set或list

  • <collection>:一对多;javaType=”集合/数组 ”,ofType=”关联对象”;连表查询

    • 鉴别器:根据表中某个字段区别数据,将查询结果自动封装成不同类型的对象;父类如People,根据属性sex鉴别子类Male和Famale;父类分类查询时,< resultMap>下使用鉴别器< discrimitor column="鉴别列">,其下< case value="鉴别列的值1…" resultType="子类">< reslut column="子类自有的列名,即另一个子类中没有的属性和数据库中值为null的列" property="属性名">

  • 动态SQL语句:基于OGNL表达式,<where><if><set><choose><when><otherwise><trim><foreach>;< foreach>一般放在in条件语句中,批量删除>;动态sql在条件查询时即可修改特定列(另一种方式先查询再修改,则执行了两次数据库操作);不能省略“and”或"or

    • 根据时间查询:报错Error querying database. ...DateTime and java.lang.String;原因是MyBatis的xml文件中写的if为空判断类型出错,把日期类型与空串作比较,代码片段如下:<if test="createTime != null and createTime != ''">;解决:createTime为日期类型,去掉 and createTime != ''

    • IN语句的使用:在SQL中可以直接支持如:where userName IN ('A', 'B')或IN (A,B);但Mybatis中传入userName="A,B";再用where userName IN (#{userName})获取,则不支持;可简单替换为$,使其直接替换为IN (A,B)形式,但这无法反防止SQL注入问题,最好还是用# + 集合遍历的方式:方法传参分别是List,Map,String[]等集合类型;遍历 WHERE id IN <foreach collection="list\array" item="id" index="index" open="(" close=")" separator=","> #{id} </foreach>

    • 动态查询:

      • <if>:and关系,符合条件的选项都匹配

      • <choose-when-otherwise>:or关系;从多个选项中选择一个;when列表依次执行,test一旦有一个成立,则执行其下的SQL且choose结束;都不成立则执行otherwise;类似if..else if...else;

      • <trim>:格式化标签,可以完成<set>或<where>标记的功能。参数:prefix:给SQL添加前缀;prefixOverrides:去掉SQL中的第一个指定值;suffix:添加后缀;suffixOverrides:去掉最后一个指定值

      • <bind>标签用法:不同数据库的Concat函数用法不一样,如Mysql的有3个参数,Oracle的有2个;为了避免这条SQL在不同环境中运行出错,使用ONGL(Object-Graph Navigation Language, 对象图形化导航语言)表达式创建一个变量并将其绑定到上下文中

      • 过往实践:动态SQL与Group by和select的集合运用,以及去掉首尾的某些符号:

        SELECT count(1) AS number
            <choose>
                <when test="param.startTime != null and param.endTime != null">
                    , DATE_FORMAT(create_time, '%Y-%m-%d') AS createTime
                </when>
                <when test="">
                    <if test="param.eventcaseName != null and param.eventcaseName != ''">
                        , eventcase_name
                    </if>
                    <if test="param.eventcaseLabel != null and param.eventcaseLabel != ''">
                        , eventcase_label
                    </if>
                    ......
                </when>
                <otherwise>
                    , event_type
                </otherwise>
            </choose>
            FROM (
                SELECT * FROM <include refid="table" /> where 1 = 1
                <if test="param.eventcaseName != null and param.eventcaseName != ''">
                    and eventcase_name like concat('%', #{param.eventcaseName}, '%')
                </if>
                <if test="param.eventcaseLabel != null and param.eventcaseLabel != ''">
                    and eventcase_label like concat('%', #{param.eventcaseLabel}, '%')
                </if>
                ......
            ) AS A
            GROUP BY
                <choose>
                    <when test="param.startTime != null and param.endTime != null">
                        DATE_FORMAT(A.create_time, '%Y-%m-%d');
                    </when>
                    <when test="">
                        <trim prefixOverrides=",">
                            <if test="param.eventcaseName != null and param.eventcaseName != ''">
                                , eventcase_name
                            </if>
                            <if test="param.eventcaseLabel != null and param.eventcaseLabel != ''">
                                , eventcase_label
                            </if>
                            ......
                        </trim>
                    </when>
                    <otherwise>
                        event_type
                    </otherwise>
                </choose>
        
  • idea不会编译src下的java目录下的xml文件,所有mapper.xml需要放在resources下

  • 业务中调用接口的增删改方法时需要session.commit()事务提交,或者使用注解开启事务支持,才会保存数据到DB

  • 编写SQL:

    • 注解方式:

      • 编写带有SQL相关注解和SQL语句的mapper类

      • 编写mybatis相关配置类,如配置当数据库列名和实体类属性名不一样(如驼峰命名和_连接)

      • 使用@Configuration添加一个重写了ConfigurationCustomizer类中方法的@Bean

      • 当mapper类太多时,可在启动类上使用@MapperScan指定扫描包,批量操作

         @Mapper
         @Component
         public interface EmployeeMapper {
             @Select("select * from employee where id = #{id}")
             public Employee queryEmployeeById(Integer id);
         ​
             @Select("select * from Employee")
             public List<Employee> queryEmployees();
         ​
             @Delete("delete from employee where id = #{id}")
             public int deleteEmployeeById(Integer id);
         ​
             // 使用自增长策略,指定主键
             @Options(useGeneratedKeys = true, keyProperty = "id")
             @Insert("insert into employee(name) values(#{name})")
             public int addEmployee(Employee employee);
         ​
             @Update("update employee set name = #{Employee} where id = #{id}")
             public int updateEmployee(Employee employee);
         }
      • SQL-XML映射方式(推荐,支持更复杂的动态SQL)

  • $:

    • 配合模糊查询,纯粹的字符串替换后显示

    • 一般用于传入数据库对象,如参数为表名、列名或order by时;参数是字符串时用引号

    • select * from user where name = ${name},解析为:select * from user where name = 实际值,此时如果恶意构造值为name = 'a' or 1 = 1,直接替换后为:select * from user where name = 'a' or 1 = 1;就形成了SQL注入的安全问题

  • #:推荐

    • 在动态解析/预编译时以安全值如?填充,然后调用PreparedStatement的set()赋值,传入的字符串被添加单引号;防止SQL注入;预编译效率高

    • select * from user where name = #{name},解析为:select * from user where name = ?,在代码中由参数值来初始化?的实际值;此时如果恶意构造值为name = 'a' or 1 = 1,预编译后为select * from user where name = " 'a' or 1 = 1";此时检查不通过

1.3 分页

  • 逻辑分页:查询所有数据,然后根据页码选出数据显示;参数currentPage和pageSize;offset和limit

  • 物理分页:判断出应该选的数据并查询

  • PageHelper:分页插件

    • 引入依赖

    • 在mybaitsConfig.xml中配置拦截器插件;或者使用Spring配置方式(略);SpringBoot似乎只需引入依赖即可?

       <plugins>
           <plugin interceptor="com.github.pagehelper.PageHelper">
               <property name="dialect" value="mysql"/>
               <!-- 该参数默认为false;设置为true时,会将RowBounds第一个参数offset当成pageNum页码使用 -->
               <!-- 和startPage中的pageNum效果一样-->
               <property name="offsetAsPageNum" value="true"/>
               <!-- 该参数默认为false;设置为true时,使用RowBounds分页会进行count查询 -->
               <property name="rowBoundsWithCount" value="true"/>
               <!-- 设置为true时,如果pageSize=0或者RowBounds.limit = 0就会查询出全部的结果 -->
               <!-- (相当于没有执行分页查询,但是返回结果仍然是Page类型)-->
               <property name="pageSizeZero" value="true"/>
               <!-- 3.3.0版本可用 - 分页参数合理化,默认false禁用 -->
               <!-- 启用合理化时,如果pageNum<1会查询第一页,如果pageNum>pages会查询最后一页 -->
               <!-- 禁用合理化时,如果pageNum<1或pageNum>pages会返回空数据 -->
               <property name="reasonable" value="true"/>
           </plugin>
       </plugins>
    • 在Service层中,调用Dao层执行查询之前使用,紧接着的第一个select的SQL将执行分页

       // Mapper接口方式调用
       Page<Object> objects = PageHelper.startPage(1, 10);
       ​
       // RowBounds方式调用
       List<Object> objects = sqlSession.selectList("x.y.selectIf", null, new RowBounds(1, 10));
       ​
       // 把objects封装到PageInfo,便可以使用其中的属性,如pageNum,pageSize,size,startRow,endRow,pages,prePage,nextPage,isFirstPage,isLastPage,hasNextPage...
       PageInfo<Object> objectPageInfo = new PageInfo<>(objects);

  • @PageableDefault:SpringJPA提供的分页注解

     public Page<Object> queryByPage(@PageableDefault(value = 10, sort = {"id"}, direction = Sort.Direvtion.DESC) Pageable pageable) {
         // pageable传入SQL调用的方法中,该对象包含了分页信息,如pageSize
     }

1.4 整合Spring/SpringBoot

  • Mybatis已经能操作数据库了,但与Spring整合,将类管理交给容器,省略掉SqlSession等相关代码,如Spring配置好数据源,< bean>定义SqlSessionFactoryBean并注入数据源属性即可;事务跟从Spring

  • 整合思路:将mybatis的配置改写到spring的ApplicationContext.xml

    • SqlSessionFactoryBean:封装SqlSessionFactory创建的代码;< bean>注册到容器,< property>注入各种属性

    • MapperScannerConfigure:封装SqlSession创建和bean获取的代码;bean的id值为类首字母小写

      <bean id="ssf" class="SqlSessionFactoryBean">
          <property name="dataSource" ref="指定dataSource" />
          <property name="typeAliasesPackage" value="映射的实体" />
          <property name="mapperLocations" "映射的mapper文件,如classpath:com../*Mapper.xml" />
      </bean>
      
      <bean id="scaner" class="MapperScannerConfigure">
          <property name="sqlSessionFactoryBeanName" value="ssf" />
          <property name="basePackage" value="扫描dao接口所在的包" />
      </bean>
  • 引入连接Spring的jar:mybatis-spring

  • 原始mybatis创建数据连接时设置Connection.setAutoCommit(false),即需要手动操作事务的开关;整合后Conncetion交由连接池管理,连接池保持了事务自动提交机制

  • 详解:SSM框架——详细整合教程(Spring+SpringMVC+MyBatis)_AndyLizh的专栏-CSDN博客_ssm框架

  • TooManyResultsException:mybatis异常,期望获得一个结果,但查询出了多个;方法返回类型与SQL执行结果不匹配

  • ReflectionExcepton:There is no getter for property named:dao层方法参数与mapper中不一致;可用@Param限制参数名

  • 整合SpringBoot:
    • 在application.yml中配置Mybatis相关设置
    • 日志打印Mybatis的SQL语句:在application.yml中配置logging-level:
      logging:
          level:
              com.xxx.xxx.*: debug
              # 此路径为Mybatis对应的方法(一般为dao或mapper接口)所在的包,而不是mapper.xml所在的包

1.5 mybaits-generatorConfiguration

  • generator插件:dao层与mybatis.xml文件的映射

1.6 Free mybatis plugin

  • dao层方法与xml文件中方法快速跳转;mybatis自动补全和语法错误提示

  • Plugins中搜索Free mybatis plugin,install即可

1.7 缓存

  • 默认开启一级缓存(不用配置),不开启二级缓存(开启需要配置;spring项目默认也开启了二级缓存,但需要配置才能使用)

  • 优先查询二级缓存,没有的话再查询一级缓存,最后查询数据库

  • 一级缓存

    • 具有和SqlSession相同的的生命周期,SqlSession相当于一个JDBC的Connection对象,在一次请求事务会话后其关闭

  • 二级缓存:

    • 相同的Mapper类,相同的sql语句,不同的SqlSession却会查询数据库两次,需要二级缓存解决

    • 作用域为Mapper级别,即可以在SqlSessionFactory层面上(全局单例,对应一个数据库连接池,应用启动后一直存在)能提供给各个SqlSession共享

    • 执行相同namespace下的mapper映射文件并执行commit操作时,会清空该namespace下的二级缓存

  • 配置:< cache>标签,属性包括:

    • eviction="LRU"

      • 缓存回收策略,包括:

        • LRC:最近最少使用策略,移除最长时间不使用的对象

        • FIFO:先进先出策略

        • SOFT:软引用,移除基于垃圾回收器的状态和软引用规则的对象

        • WEAK:弱引用,更积极地移除基于垃圾回收期的状态和弱引用规则的对象

    • flushInterval="100000":刷新间隔时间

    • size="512":引用数目,代表缓存最多可以储存多少个对象

    • readOnly="true":只读,意味着缓存数据只能读取,不能修改,好处是可以快速读取缓存,缺点是没有办法修改缓存,默认值false(可读/可写)

  • 自定义缓存:Redis,RedisTemplate

    • 内置缓存机制不常用,因为缓存结果要存储在当前服务器的JVM中,数据量过大会造成jvm服务器臃肿缓慢,大量占用内存空间

    • 将缓存的数据从tomcat服务器上转移到redis服务器上,redis存取速度快,并且是一个独立进程和jvm没有关系

2 MybatisPlus

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值