Home Tags Posts tagged with "mybatis"

mybatis

0 53

有一个需求,需要新增一个查询接口,我原本是直接写到xml文件里面的,一切都好

但是组长在代码CR的时候说,该xml文件是Mybatis-Plus自动生成的,如果我们将自定义SQL写在其中

那么,下次当我们再使用Mybatis-Plus自动生成时,会覆盖掉我们的自定义SQL

所以需要将自定义SQL写在自己定义的xml文件中 或者 采用注解的方式

当我写好@select之后,却发现查询出来始终为空,而在Navicat中 直接复制SQL查询是可以查到的,因此我怀疑是结果映射的问题。

而网上真他妈不好搜Mybatis注解的结果映射,这玩意儿真的很反人类,还好,最好搜到了相关资料

(36条消息) @Results与@ResultMap使用_蜗牛-的博客-CSDN博客_@resultmap

然后我直接 @ResultMap(“BaseResultMap”) 注:BaseResultMap在该Dao对应的xml文件中有定义,故可行。

 

让我们从JDBC一步一步分析到ORM框架

 

ORM概念说明

一、什么是JDBC?

 

二、为什么是对象关系映射(ORM)?

当我们工作在一个面向对象的系统中时,存在一个对象模型和关系数据库不匹配的问题。RDBMSs 用表格的形式存储数据,然而像 Java 或者 C# 这样的面向对象的语言它表示一个对象关联图。考虑下面的带有构造方法和公有方法的 Java 类:

现考虑以上的对象需要被存储和索引进下面的 RDBMS 表格中:

第一个问题,如果我们开发了几页代码或应用程序后,需要修改数据库的设计怎么办?

第二个问题,在关系型数据库中加载和存储对象时我们要面临以下五个不匹配的问题。

Object-Relational Mapping (ORM) 是解决以上所有不匹配问题的方案。

 

三、什么是ORM?

对象关系映射(英语:Object Relational Mapping,简称ORM,或O/RM,或O/R mapping),是一种程序设计技术,用于实现面向对象编程语言里不同类型系统数据之间的转换。

显然,这句话说的云里雾里的,我们对其进行翻译:其主要作用是在编程中,把面向对象的概念跟数据库中表的概念对应起来。举例来说就是,我定义一个对象(实体),那就对应着一张表,这个对象的实例,就对应着表中的一条记录。

一个 ORM 系统相比于普通的 JDBC 有以下的优点:

 

 

四、Java ORM框架

在 Java 中有几个持久化的框架和 ORM 选项。一个持久化的框架是 ORM 存储和索引对象到关系型数据库的服务。

  • Enterprise JavaBeans Entity Beans
  • Java Data Objects
  • Castor
  • TopLink
  • Spring DAO
  • Hibernate
  • And many more                                            资料来源:Hibernate ORM 概览_w3cschool

 

ORM框架必备知识点

一、Hibernate的优秀之处与短板

Hibernate优点

(1) 对象/关系数据库映射(ORM)
它使用时只需要操纵对象,使开发更对象化,抛弃了数据库中心的思想,完全的面向对象思想
(2) 透明持久化(persistent)
带有持久化状态的、具有业务功能的单线程对象,此对象生存期很短。这些对象可能是普通的JavaBeans/POJO,这个对象没有实现第三方框架或者接口,唯一特殊的是他们正与(仅仅一个)Session相关联。一旦这个Session被关闭,这些对象就会脱离持久化状态,这样就可被应用程序的任何层自由使用。(例如,用作跟表示层打交道的数据传输对象。)
(3) 事务Transaction(org.hibernate.Transaction)
应用程序用来指定原子操作单元范围的对象,它是单线程的,生命周期很短。它通过抽象将应用从底层具体的JDBC、JTA以及CORBA事务隔离开。某些情况下,一个Session之内可能包含多个Transaction对象。尽管是否使用该对象是可选的,但无论是使用底层的API还是使用Transaction对象,事务边界的开启与关闭是必不可少的。
(4) 它没有侵入性,即所谓的轻量级框架
(5) 移植性会很好
(6) 缓存机制,提供一级缓存和二级缓存
(7) 简洁的HQL编程

Hibernate缺点

(1) Hibernate在批量数据处理时有弱势
(2) 针对单一对象简单的增删查改,适合于Hibernate,而对于批量的修改,删除,不适合用Hibernate,这也是OR框架的弱点;要使用数据库的特定优化机制的时候,不适合用Hibernate

 

二、Mybatis和Hibernate相比,优势在哪里

链接:https://www.zhihu.com/question/21104468/answer/92986556

测试分析

测试分成了插入,单表查询,关联查询。关联查询中hibernate分成三种情况进行配置。其中在关联字段查询中,hibernate在两种情况下,性能差异比较大。 都是在懒加载的情况下,如果推特对应的用户比较多时,则性能会比仅映射100个用户的情况要差很多。

换而言之,如果用户数量少(关联的总用户数)时,也就是会重复查询同一个用户的情况下,则不需要对用户表做太多的查询。

其中通过查询文档后,证明使用懒加载时,对象会以id为key做缓存,也就是查询了100个用户后,后续的用户信息使用了缓存,使性能有根本性的提高。甚至要比myBatis更高。

如果是关联50万用户的情况下,则hibernate需要去查询50万次用户信息,并组装这50万个用户,此时性能要比myBatis性能要差,不过差异不算大,小于1ms,表示可以接受。

其中hibernate非懒加载情况下与myBatis性能差异也是相对其他测试较大,平均值小于1ms。

这个差异的原因主要在于,myBatis加载的字段很干净,没有太多多余的字段,直接映身入关联中。反观hibernate则将整个表的字都会加载到对象中,其中还包括关联的user字段。

hibernate这种情况下有好有坏,要看具体的场景,对于管理平台,需要展现的信息较多,并发要求不高时,hibernate比较有优势。

然而在一些小活动,互联网网站,高并发情况下,hibernate的方案太不太适合,myBatis+VO则是首选。

测试总结

总体初观,myBatis在所有情况下,特别是插入与单表查询,都会微微优于hibernate。不过差异情况并不明显,可以基本忽略差异。

差异比较大的是关联查询时,hibernate为了保证POJO的数据完整性,需要将关联的数据加载,需要额外地查询更多的数据。这里hibernate并没有提供相应的灵活性。

关联时一个差异比较大的地方则是懒加载特性。其中hibernate可以特别地利用POJO完整性来进行缓存,可以在一级与二级缓存上保存对象,如果对单一个对象查询比较多的话,会有很明显的性能效益。

以后关于单对象关联时,可以通过懒加载加二级缓存的方式来提升性能。

最后,数据查询的性能与orm框架关无太大的关系,因为orm主要帮助开发人员将关系数据转化成对象型数据模型,对代码的深析上来 看,hibernate设计得比较重量级,对开发来说可以算是重新开发了一个数据库,不让开发去过多关心数据库的特性,直接在hibernate基础上进 行开发,执行上分为了sql生成,数据封装等过程,这里花了大量的时间。然而myBatis则比直接,主要是做关联与输出字段之间的一个映射。其中sql 基本是已经写好,直接做替换则可,不需要像hibernate那样去动态生成整条sql语句。

好在hibernate在这阶段已经优化得比较好,没有比myBatis在性能上差异太多,但是在开发效率上,可扩展性上相对myBatis来说好太多。

最后的最后,关于myBatis缓存,hibernate查询缓等,后续会再专门做一篇测试。

关于缓存配置

myBatis相对Hibernate 等封装较为严密的ORM 实现而言,因为hibernate对数据对象的操作实现了较为严密的封装,可以保证其作用范围内的缓存同步,而ibatis 提供的是半封闭的封装实现,因此对缓存的操作难以做到完全的自动化同步。

以上的缓存配置测试仅为性能上的分析,没有加入可用性上的情况,因为myBatis直接配置缓存的话,可能会出现脏数据,。

在关联查询数据的情况下,hiberntae的懒加载配二级缓存是个比较好的方案(无脏数据),也是与myBatis相比有比较明显的优势。此情景下,性能与myBatis持平。

在真实情况下,myBatis可能不会在这个地方上配置缓存,会出现脏数据的情况,因而很有可能在此hibernate性能会更好。

 

三、springjpa和mybatis哪个查询效率高?

 

四、Mybatis Hebernate 学习资料

Mybatis(真的超级超级详细) – 知乎 (zhihu.com)

快速入门MyBatis第一天(共两天) – 知乎 (zhihu.com)

Hibernate 教程_w3cschool

 

Springboot整合Mybatis的步骤:

1.数据源配置:在application.properties里面

#配置数据源
spring.datasource.url=jdbc:mysql://localhost:3306/transactional_springboot?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
spring.datasource.username=root
spring.datasource.password=123456789
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

2.(前提是导入依赖)实体类,Mapper接口的编写

@Repository
public interface UserMapper {
    void insertUser(User user);
}
@Data
@Repository
public class User {
    private String name;
    private int id;
}

3.Mapper.xml文件编写

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

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

    <insert id="insertUser" parameterType="com.yang.pojo.User">
          insert into  user(id,name) values (#{id},#{name});
    </insert>
</mapper>
4.Controller层编写
@RestController
public class InsertUserController {

    @Autowired
    UserMapper userMapper;
    @Autowired
    User user;
    @PostMapping("/insert")
    public void insert(){
        user.setId(10);
        user.setName("First");
        userMapper.insertUser(user);
    }
}
5.测试类编写
@SpringBootTest(classes = Application.class)
public class Test {

    @Autowired
    InsertUserController insertUserController;

    @org.junit.jupiter.api.Test
    public void go(){
        insertUserController.insert();
    }
}

坑点:Mapper接口文件不仅要有@Repository注解(标记为Bean,给Spring管理)还要有@Mapper注解才可 !!!否则会报
Field userMapper in com.yang.controller.InsertUserController required a bean of type 'com.yang.mapper.UserMapper' that could not be found.
找不到Mapper文件错误

1.导入依赖(Mybatis)

<!--        mybatis自带的-->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.0</version>
        </dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <scope>runtime</scope>
</dependency>

2.编写配置文件连接数据库(application.properties)
spring.datasource.username=root
spring.datasource.password=123456789
spring.datasource.url=jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

以下为具体测试:

3.编写实体类(Pojo)

import lombok.Data;

@Data
public class User {
    private int id;
    private String name;
    private String pwd;
}
4.IDEA连接数据库

5.实体类写好编写Mapper接口

或者
6.在Mapper.xml中编写sql语句
<mapper namespace="com.yang.dao.UserMapper">

    <select id="getUserList" resultType="PoJo.User">
        select * from mysql.mybatisuser
    </select>

    <select id="getUserLike" parameterType="map" resultType="PoJo.User">
        select * from mysql.mybatisuser where name like #{value};
    </select>


    <select id="getUserById" parameterType="int" resultType="PoJo.User">
        select * from mysql.mybatisuser where id = #{userid};
    </select>

    <select id="getUserById2" parameterType="map" resultType="PoJo.User">
        select * from mysql.mybatisuser where id = #{id};
    </select>

    <insert id="addUser" parameterType="PoJo.User">
        insert into mysql.mybatisuser (id,name,pwd) values (#{id},#{name},#{pwd});
    </insert>
<!--    <insert id="addUser2" parameterType="map">-->
<!--        insert into mysql.mybatisuser (id,name,pwd) values (#{userid},#{username},#{password});-->
<!--    </insert>-->
    <update id="updateUser" parameterType="PoJo.User">
        update mysql.mybatisuser set name = #{name},pwd = #{pwd} where id = #{id};
    </update>

    <delete id="deleteUser" parameterType="int">
        delete from mysql.mybatisuser where id = #{id};
    </delete>
</mapper>

7.Springboot整合Mybatis
#整合Mybatis
mybatis.type-aliases-package=com.yang.pojo   //包下实体类起别名
mybatis.mapper-locations=classpath:mybatis/mapper/*.xml    //Mapper的地址
其他配置也写在这里

8.编写Controller,调用Dao层(Mapper接口)
@RestController
public class UserController {

    @Autowired
    private UserMapper userMapper;

    @GetMapping("userList")
    public List<User> queryUserList(){
        List<User> userList = userMapper.queryUserList();
        for (User user : userList) {
            System.out.println(user);
        }
        return userList;
    }
}

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

<mapper namespace="com.yang.dao.UserMapper">

学习到Mybatis分页时,了解到可以使用Pagehelper分页工具(开源)

该工具可以通过Maven导入项目,使用起来非常方便,但有几个要注意的点,特此记录!

 

1.导入Jar包

<!--        引入分页插件PageHelper5-->
        <!-- https://mvnrepository.com/artifact/com.github.pagehelper/pagehelper -->
        <dependency>
            <groupId>com.github.pagehelper</groupId>
            <artifactId>pagehelper</artifactId>
            <version>4.1.4</version>
        </dependency>

2.直接在Controller层调用PageHelper.startPage()方法
坑点:
需要分页的SQL语句后面不能加;!!!否则会报如下错误
正确的sql语句:

1.#{}和${}的区别是什么?

  • #{}是预编译处理,${}是字符串替换。
  • Mybatis在处理#{}时,会将sql中的#{}替换为?号,调用PreparedStatement的set方法来赋值;
  • Mybatis在处理${}时,就是把${}替换成变量的值。
  • 使用#{}可以有效的防止SQL注入,提高系统安全性。

2、当实体类中的属性名和表中的字段名不一样 ,怎么办 ?

第1种: 通过在查询的sql语句中定义字段名的别名,让字段名的别名和实体类的属性名一致 
    <select id=”selectorder” parametertype=”int” resultetype=”me.gacl.domain.order”> 
       select order_id id, order_no orderno ,order_price price form orders where order_id=#{id}; 
    </select> 
第2种: 通过<resultMap>来映射字段名和实体类属性名的一一对应的关系 
    <select id="getOrder" parameterType="int" resultMap="orderresultmap">
        select * from orders where order_id=#{id}
    </select>
   <resultMap type=”me.gacl.domain.order” id=”orderresultmap”> 
        <!–用id属性来映射主键字段–> 
        <id property=”id” column=”order_id”> 
        <!–用result属性来映射非主键字段,property为实体类属性名,column为数据表中的属性–> 
        <result property = “ordernocolumn =”order_no”/> 
        <result property=”price” column=”order_price” /> 
    </reslutMap>

3、 模糊查询like语句该怎么写?

第1种:在Java代码中添加sql通配符。
    string wildcardname = “%smi%”; 
    list<name> names = mapper.selectlike(wildcardname);

    <select id=”selectlike”> 
     select * from foo where bar like #{value} 
    </select>
第2种:在sql语句中拼接通配符,会引起sql注入
    string wildcardname = “smi”; 
    list<name> names = mapper.selectlike(wildcardname);

    <select id=”selectlike”> 
     select * from foo where bar like "%"#{value}"%"
    </select>

4、通常一个Xml映射文件,都会写一个Dao接口与之对应,请问,这个Dao接口的工作原理是什么?Dao接口里的方法,参数不同时,方法能重载吗?

Dao接口,就是人们常说的Mapper接口,接口的全限名,就是映射文件中的namespace的值,接口的方法名,就是映射文件中MappedStatement的id值,接口方法内的参数,就是传递给sql的参数。Mapper接口是没有实现类的,当调用接口方法时,接口全限名+方法名拼接字符串作为key值,可唯一定位一个MappedStatement,举例:com.mybatis3.mappers.StudentDao.findStudentById,可以唯一找到namespace为com.mybatis3.mappers.StudentDao下面id = findStudentById的MappedStatement。在Mybatis中,每一个<select>、<insert>、<update>、<delete>标签,都会被解析为一个MappedStatement对象。

Dao接口里的方法,是不能重载的,因为是全限名+方法名的保存和寻找策略。

Dao接口的工作原理是JDK动态代理,Mybatis运行时会使用JDK动态代理为Dao接口生成代理proxy对象,代理对象proxy会拦截接口方法,转而执行MappedStatement所代表的sql,然后将sql执行结果返回。

 

5、Mybatis的Xml映射文件中,不同的Xml映射文件,id是否可以重复?

不同的Xml映射文件,如果配置了namespace,那么id可以重复;如果没有配置namespace,那么id不能重复;

原因就是namespace+id是作为Map<String, MapperStatement>的key使用的,如果没有namespace,就剩下id,那么,id重复会导致数据互相覆盖。有了namespace,自然id就可以重复,namespace不同,namespace+id自然也就不同。

备注:在旧版本的Mybatis中,namespace是可选的,不过新版本的namespace已经是必须的了。

6、Mybatis是如何进行分页的?分页插件的原理是什么?

Mybatis使用RowBounds对象进行分页,它是针对ResultSet结果集执行的内存分页,而非物理分页。可以在sql内直接书写带有物理分页的参数来完成物理分页功能,也可以使用分页插件来完成物理分页。

分页插件的基本原理是使用Mybatis提供的插件接口,实现自定义插件,在插件的拦截方法内拦截待执行的sql,然后重写sql,根据dialect方言,添加对应的物理分页语句和物理分页参数。

 

7、简述Mybatis的插件运行原理,以及如何编写一个插件。

答:Mybatis仅可以编写针对ParameterHandler、ResultSetHandler、StatementHandler、Executor这4种接口的插件,Mybatis使用JDK的动态代理,为需要拦截的接口生成代理对象以实现接口方法拦截功能,每当执行这4种接口对象的方法时,就会进入拦截方法,具体就是InvocationHandler的invoke()方法,当然,只会拦截那些你指定需要拦截的方法。

编写插件:实现Mybatis的Interceptor接口并复写intercept()方法,然后再给插件编写注解,指定要拦截哪一个接口的哪些方法即可,最后在配置文件中配置你编写的插件。

 

8.Mybatis 分页

一、回忆Mybatis流程

1.编写实体类

2.编写核心配置文件

3.编写接口

4.编写Mapper.xml(使用注解可以不用)

5.测试

二、回忆Spring流程

1.编写Bean类

2.编写核心配置文件

3.通过核心配置文件创建Bean

4.测试

 

Spring整合Mybatis(两种方法)

明显变化:整合之后无Mybatis工具类(Spring替我们生产Factory)

方法一:

步骤:

1.整合数据源

2.sqlSessionFactory

3.sqlSessionTemplate

4.需要给接口加实现类(上图方法一,下图方法二)

方法二:继承SqlSessionDaoSupport类,调用getSqlSession()方法生成sqlSession;

为什么要实现类:

而在Spring中为实现面向对象,需要是实现类来实现(多了一层,Bean中不能直接放XML,本质还是通过XML实现)

5.将自己写的实现类注入到Spring中

6.测试即可

项目结构:注:(需要为对应操作)

  • 1.2为Mybatis接口及Mapper(二者是要实现的Sql操作)
  • 3.配置数据库,SqlSessionFactory和SqlSession
  • 4.接口实现类(在Mybatis中)
  • 5.将接口实现类放入Spring容器
  • 6.测试

主要代码:

spring-dao.xml

 

<?xml version="1.0" encoding="UTF8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd">
    <!--    id:bean的名字,class,实现类的全限定名-->

        <!--    通过无参构造创建对象:<property name="name" value="杨某"/>-->
        <!--    通过下标赋值创建对象<constructor-arg index="0" value="杨哥学Java"></constructor-arg>-->
        <!--    通过类型创建对象(不推荐使用)<constructor-arg type="java.lang.String" value="OKK">-->
        <!--    直接通过参数名来创建对象-->


    <!--    如果添加了别名,可以使用别名来获取对象-->

    <!--    合作开发,导入配置文件-->



<!--    1.使用Spring的数据源替换Mybatis的配置-->
<!--    使用Spring提供的JDBC:org.springframework.jdbc.datasource.DriverManagerDataSource-->
    <bean id = "datasource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
            <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
            <property name="url" value="jdbc:mysql://localhost:3306/mysql?useUnicode=true&amp;characterEncoding=UTF-8&amp;serverTimezone=Asia/Shanghai"/>
            <property name="username" value="root"/>
            <property name="password" value="123456789"/>
    </bean>

<!--    2.SqlSessionFactory-->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="datasource"/>
<!--    绑定mybatis配置-->
        <property name="configLocation" value="classpath:mybatis-config.xml"/>
        <property name="mapperLocations" value="classpath:com/yang/mapper/UserMapper.xml"/>
    </bean>

<!--    3.只能使用构造器注入SqlSessionFactory,因为它没有Set方法-->
    <bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
        <constructor-arg index="0" ref="sqlSessionFactory"/>
    </bean>

</beans>


sqlSessionTemple使用方法:
1.通过org.mybatis.spring.SqlSessionTemplate生成sqlSession
2.通过唯一的构造方法,注入sqlSessionFactory
3.实现:创建实现类,做一件事情,将sqlSession私有,通过实现类注入以支持业务层调用