mybatis入门学习

本文介绍了Mybatis的基础知识,包括与Hibernate的对比、核心配置文件sqlMapConfig.xml的设置、Mapper映射文件的创建、如何处理不能自动映射的情况、动态SQL的使用以及特殊字符的处理。此外,还详细探讨了#{}与${}的区别,JDBC类型的使用,以及对一和对多关系映射的配置方法,强调了在Mybatis中配置对象关系映射的实际作用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

  • mybatis和hibernate的比较
首先说一下两个持久层框架的前生,EJB,它是一个重量级的框架,同时具有天然分布式,自动负载均衡的优点,但是,它的致命缺点是不够灵活,不能充分适应业务,有些场景其实不需要分布式的特点它也提供。多条件查询时,使用EntityBean,但是每一种查询条件就得写一条sql,很麻烦,后面有了SessionBean的解决方案,但是同时就没有原生EntityBean自带的EJB那些好处了。
后面由于这些缺点,hibernate出现了,它是一个面向对象的持久层框架,可以让整个开发流程全部面向对象来开发,属于全ORM。
iBatis,mybatis的前生,维持原有的jdbc方式,前面部分使用面向对象的方式,具体到持久层时就使用sql这种面向过程的方式。属于半ORM。
总结下来就是
1)hibernate比较适合中小型项目,对性能要求不高,因为hibernate虽然是面向对象,但是底层仍然是sql,只不过是hql转为sql,这存在性能问题,同时sql对程序员不可见,造成sql无法优化。
2)hibernate是通过反射来完成映射的,性能很低。
3)iBatis是对jdbc的轻量级封装,它是很接近原生jdbc的性能,且使用sql,性能比较高,适合大型项目需要做很多sql优化的情况。
使用mybatis一般也很少做表与表之间的约束,而是通过代码来维护外键关系,因为如果外键这种东西存在的意义就是防止业务出错,加了外键就相当于要把这部分逻辑交给数据库去判断,无疑增加了数据库的压力,所以用代码去手动维护能更好的减轻数据库的压力,毕竟都是为了防止业务出错,不加外键只是代表把业务校验逻辑从数据库转移到了应用服务器。
具体到实际使用上,它两者的差别总结如下:
1)hibernate在对象这一块有一个PO实体对象,然后配置该对象的映射文件如User.hbm.xml,里面需要配置实体对象与表字段之间的关系,以及它与其他对象之间的一对多、多对一等关系,然后是核心配置文件hibernate.cfg.xml,最后就是SessionFactory获取Session。

2)mybatis是首先创建一个PO实体对象,这点一样,然后是配置该对象的映射关系文件UserMapper.xml,里面配置实体对象与表字段之间的对应关系,这点也一样,但是mybatis是没有hibernate那种需要配置各种对象与对象之间关系的映射配置,最后是核心配置文件sqlMapConfig.xml和SqlSessionFactory获取SqlSession。

  • 核心配置文件
1)环境配置

<!-- mybatis提供环境切换配置,即不同环境可以选择不同的配置 如开发环境选development  线上环境选deploy -->
<!-- 和spring整合后 environments配置将废除-->
<environments default="development">
    <environment id="development">
        <!--支持两种事务管理  JDBC/MANAGED(mybatis不予管理,自主管理,即交给有事务容器的框架管理)-->
        <!-- 使用jdbc事务管理-->
        <transactionManager type="JDBC"/>
        <!-- 数据库连接池  POOLED/UNPOOLED/JNDI  前两种就是说是否要使用连接池。最后一个是JNDI,这里简单说一下JNDI的概念。
                JNDI可以屏蔽底层数据库,它是自己模拟出一个服务器然后给应用服务器提供一个url去连接到数据库,url与底层数据库没有关系,
                不管是MySQL还是Oracle甚至Access都是一样的。这样做有个好处就是防止别人知道底层数据库,同时使用url能加密对数据库的访问。
              -->
        <dataSource type="POOLED">
            <property name="driver" value="com.mysql.jdbc.Driver"/>
            <property name="url" value="jdbc:mysql://localhost:3306/mybatis?charsetEncoding=utf8"/>
            <property name="username" value="root"/>
            <property name="password" value="root"/>
        </dataSource>
    </environment>
</environments>
  • Mapper映射文件
每个Mapper有一个namespace,namespace是该Mapper的命名空间,用于区分其他Mapper。没有特定名称,只要全局不重复即可。
<!--id为该条SQL的名称,在该Mapper下不能重复
resultType里填写集合泛型,不论返回值是List还是单个类型,都填写泛型或者单个类型即可,mybatis会自动猜测返回值是一个类型还是集合-->
<select id="findAll" resultType="domain.User">
SELECT * FROM sys_user
</select>
对于输入类型,有parameterMap和parameterType两种,前者已经过时,是iBatis时代的产物,现在只是用parameterType一种即可,它对应mybatis定义的一种数据类型,通过查看mybatis源码可以看到,定义在一个枚举类型中。同时很多情况下,可以不写parameterType,直接去取参数也可以,但是resultType不行。
对于查询集合,使用session.seleceList即可。这里说一下查询单条记录selectOne。
对于查询单条记录,必须保证查询出来的记录只有一个,如果有多个mybatis是会报错的,而不是就取多条记录中的第一个。
查看mybatis底层源码后了解,不管是selectList还是selectOne,其实都是selectList,这里从贴出的源码很容易看出去。
public <T> T selectOne(String statement, Object parameter) {
    // Popular vote was to return null on 0 results and throw exception on too many.
    List<T> list = this.<T>selectList(statement, parameter);
    if (list.size() == 1) {
        return list.get(0);
    } else if (list.size() > 1) {
        throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
    } else {
        return null;
    }
}
另外一个是selectList,底层究竟是怎么把ResultSet封装到一个List中的。主要就是在ResultSet中获取到值时,通过反射知道每个对象的属性,然后就根据属性set对应的值,当然这里必须保证对象属性名和表字段名一样,否则是无法自动映射的。比如,映射menu_id到menuId是无法自动映射的,但是mybatis这里有一个配置可以开启驼峰命名的自动映射,也就是说只要符合aaa_bb映射到aaaBb这种写法的,也可以自动映射,但是需要手动配置开启。mapUnderscoreToCamelCase这是配置的name。
<settings>
    <setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
  • mybatis不能自动映射的解决办法
比如上面说的例子,加入不开启驼峰映射,user_name如何映射到username上?
1)第一种是写SQL是改一下 select id, user_name as username ... from sys_user
也就说说在SQL里写好对应的名字,从这里也看的出来mybatis自动映射是通过结果集的字段名来完成的。当然这种方案太繁琐,只是通过这个方法可以看出mybatis的映射原理。
2)使用resultMap,手动定义映射。
<!--中介作用,实现自定义表与对象字段的映射-->
<resultMap id="BaseResultMap" type="domain.User">
<!--主键映射-->
<id column="id" property="id" jdbcType="INTEGER"/>
<!--字段映射-->
<result column="user_name" property="username" jdbcType="VARCHAR"/>
<!--关系映射-->
</resultMap>
这里说一点,可以不配置完也能工作,即resultMap中没有手动写映射的,它也会自动映射。
  • mybatis提供的简化操作
首先是别名,这个简单说下,就是在mybatis的核心配置文件里配置typeAliases,让一个包名能对应一个简单的类型,比如com.me.User 对应User即可。但是这个用的很少,因为使用别名这种简称不方面识别它究竟是从什么包来的等一些详细信息。
<!-- 类型别名替换 -->
<typeAliases>
    <!--type:类型全路径 alias:别名-->
    <typeAlias type="domain.User" alias="user"/> 
    <!-- 包扫描的方式 ,里面所有类都能使用别名,别名就是类名,且不区分大小写-->
    <package name="com.me.pojo"/>
</typeAliases>
  • 动态SQL使用
动态SQL其实是指定传入参数不确定时,SQL的一些变换。
1)where标签。
这里使用where标签动态控制查询参数的判断,生成没有该查询参数的SQL。注意这里每个条件必须要写and进行连接,第一个可以写也可不写,写了where标签会自动去除第一个的and连接。
<!--传入参数为map,即代表传入参数可以任意指定要传入的key-value。但有时候要查询的参数不一定全有,所以需要动态确定是否查询该参数-->
<select id="findByNameAndSex" parameterType="map" resultType="domain.User">
    SELECT * FROM sys_user
    <where>
        <if test="name != null">username LIKE #{name}</if>
        <if test="sex != null">and sex = #{sex}</if>
    </where>
</select>
2)set标签
set标签在使用时,跟where类似,它也需要每个更新条件后面都要有个逗号,当然最后一个可以没有,但是前面的必须要有,总之记住一点,不管是where还是set,对于它的自动处理,只是去除,发现你有多的and,就会去除,但是不会说你这里语法缺少and给你添上。
<!--更新时,有时要更新的参数不是全部都修改,所以也要动态控制-->
<update id="update" parameterType="domain.User">
    UPDATE sys_user
    <set>
        <if test="username != null">username = #{username},</if>
        <if test="sex != null">sex = #{sex},</if>
        <if test="birthday != null">birthday = #{birthday},</if>
        <if test="address != null">address = #{address}</if>
    </set>
    where id = #{id}
</update>
3)foreach标签
从foreach标签的那些名称可以很明显看出他的作用。它其实就是生成(?, ?, ?)这种类似的查询条件。这里有点要注意的是里面collection必须要准确,list就是list,不能用数组代替。
<select id="findIn" parameterType="int" resultType="domain.User">
    SELECT * FROM sys_user
    WHERE id IN 
    <foreach collection="list" item="id" open="(" close=")" separator=",">
        #{id}
    </foreach>
</select>


  • 特殊字符处理
由于mapping的SQL是写在xml文件里的,而xml文件对于<小于符号是其标签开头,所以在使用到小于符号时需要说明不要对其解析。<!CDATA[ ]>
<!--特殊字符处理-->
<select id="findById" parameterType="map" resultType="domain.User">
    SELECT * FROM sys_user
    WHERE id > #{idStart} AND id <![CDATA[<]]> #{idEnd}
</select>
  • #{}与${}的区别

#{}相当于PreStatement,即预编译SQL,它运行的SQL是有一个占位符,传入的参数如果是字符串会自动在两边加上引号,即  'xiaomin'  ,好处是可以防止SQL注入。


${}相当于Statement,它会把参数与SQL拼接成字符串去查询,而不是使用占位符,这种方式的好处就是比较灵活,例如order by 查询时,可以手动传入要不要order by,但坏处就是可以SQL注入。


测试时还发现了一个小坑,如果使用#{}取值,可以不写parameterType声明,它会自动猜测并从传入参数中取值,传入一个时就不关注直接取值。使用${}的话,必须配合map使用,因为直接传入map或者po,因为它是根据${}中的key去取值,如果直接传入参数不用map包装,就会报错。也就是说,#{}传入一个时可以自动取那唯一的一个,${}传入一个参数时也必须指定对应的key,不能直接传入参数。
  • jdbcType使用
这个属性对于MySQL是可以不设置的,它主要的作用就是当传入的参数为null时,要告诉jdbc驱动程序,它针对的数据库字段类型是什么(Oracle)。
  • 对一关系映射配置

mybatis与hibernate不同,它只有两种对象关系映射,即对一和对多,并且做法也与hibernate很不同,hibernate是配置关系后自动生成查询SQL,而mybatis即使配置了对象关系,依然要自己去写查询子对象的SQL,配置对象关系的唯一作用就是在SQL查询出来后帮助映射的。这里先说对一的关系映射。

<resultMap id="BaseResultMap" type="domain.User">
    <!--主键映射 property对应对象属性 column对应数据库字段名-->
    <id column="id" property="id" jdbcType="INTEGER"/>
    <!--普通字段映射-->
    <result column="username" property="username" jdbcType="VARCHAR"/>
    <!--对象关系映射 在mybatis里只有对一和对多两种关系映射-->
    <!--association是 对一 关系映射-->
    <association property="userInfo" javaType="domain.UserInfo">
        <id property="id" column="id" />
        <result property="station" column="station"/>
        <result property="joinDate" column="join_date"/>
    </association>
</resultMap>

对一的关系映射使用association标签,在里面配置当前对象的子对象,以及子对象要映射的详细字段。配置好后进行测试,发现子对象只有UserInfo的id有值,其实这个很好相同,因为User与userinfo都是用id作为主键且都叫id,mybatis的映射原理本来就是靠SQL的结果集,发现userinfo有个id字段,结果集也有id的字段,刚好匹配,至于为什么只有id有值,是因为我们查询的时候只写了查询user信息的SQL,下面用SQL连接两张表进行查询。

<select id="findUserInfo" resultMap="UserInfoRM">
    SELECT u.id, u.username, u.birthday, u.sex, u.address, i.id, i.station, i.join_date FROM
    (SELECT * FROM sys_user) u
    LEFT JOIN
    (SELECT * FROM user_info) i
    ON u.id = i.id
</select>

查询后,发现userinfo这个子对象本来id是不存在的,但还是摄入了值,比如24号他的userinfo的id有值,这也是跟上面情况差不多,userinfo的id字段刚好对应结果集的id字段,虽然从查询图可以查出,前面的id跟后面的id不是一个表的,但mybatis对于结果集重名只认第一个。
解决方案也很简单,就是在查询SQL的时候,把userinfo的id用别名替代,比如user_info_id,然后修改ResultMap里UserInfo对应的值即可,最后对象查询出来如下,可以看出,当userinfo的所有字段都没办法从结果集中找到值对应时,连对象都不会初始化。

当然上面修改起来比较复杂,要修改SQL取别名,又要resultmap里column对应别名,但是这种情况一般是在表的字段重名的情况,所以在建表时,主键最好不要都叫id,加上表的名称更好区分。
对于有些时候不想查询出子对象,但是resultmap里面有配置了的情况,可以再另外配置一个resultmap,要它继承自没有子对象的resultmap,这样,对于查询子对象的选择就更灵活了。注意type还是User不是UserInfo,它的作用其实就是把原来的resultmap配置复制到新的resultmap里。

<resultMap id="UserInfoRM" type="domain.User" extends="UserRM">
    <!--对一使用javaType,表示要映射的这个对象是什么类型的-->
    <association property="userInfo" javaType="domain.UserInfo">
        <id property="id" column="user_info_id" />
        <result property="station" column="station"/>
        <result property="joinDate" column="join_date"/>
    </association>
</resultMap>
  • 对多关系映射配置
这里以一个User可以有多本书举例。Book表中用一个逻辑外键user_id记住它属于哪个user,在java里Book对象不需要设置user属性来记住这本书是谁的,因为没有这个需求,而User需要记住他有哪些书,这个是根据实际需求来定的,注意User里记住book的类型不是Set而是List,这点跟hibernate不同,而且这里List可以不用初始化。配置的resultMap如下,这里查询会同时封装userifno和book。
<resultMap id="UserInfoBookRM" type="domain.User" extends="UserInfoRM">
    <!--对多使用ofType,表示要映射的这个集合中的元素是什么类型的-->
    <collection property="books" ofType="domain.Book">
        <id property="id" column="id"/>
        <result property="name" column="name"/>
        <result property="money" column="money"/>
    </collection>
</resultMap>

查询的SQL如下:

SELECT
	ui.*, b.id AS book_id,
	b. NAME,
	b.money
FROM
	(
		SELECT
			u.*, i.id AS user_info_id,
			i.station,
			i.join_date
		FROM
			(SELECT * FROM sys_user) u
		LEFT JOIN (SELECT * FROM user_info) i ON u.id = i.id
	) ui
LEFT JOIN (SELECT * FROM book) b ON ui.id = b.user_id

前两条数据是一个user的,但是book是不同的信息,对于这种结果集,就体现了mybatis的resultMap封装数据的强大之处,它会判断第一条数据映射后第二条数据有些地方是重复的,就通过程序员配置的映射关系把数据存入集合中。从源码中看出,mybatis的做法是每次处理一条结果集,会把这条结果集的主键id给缓存起来,下条结果集如果还是一样的id,就会去处理结果集的映射关系。
这里再补充下,对于上面那条SQL,如果要限制过滤条件应该怎么办,最简单粗暴的办法是直接左连接三张表然后where过滤。

SELECT
	*
FROM
	sys_user u
LEFT JOIN user_info i ON u.id = i.id
LEFT JOIN book b ON u.id = b.user_id
WHERE
	u.id = 1
这种写法与上面那条SQL最后加where条件过滤效果一样,但是,这条是没办法优化的,因为它是先把三张表的所有数据连接起来,再进行过滤的,如果数据量很大的话,性能是很低的。这种查询所有数据然后再过滤的做法其实跟视图类似,都是查询表中的所有数据,如果有多张表就都查出来,最后连在一起,这样的性能可想而知,所以一般大型项目中时禁用视图的,就是因为它要查询表中的所有数据。从这里又能想到mybatis与hibernate的差异了,要查询对象间的映射关系,hibernate其实就是采用这种简单粗暴的办法,项目数据量变大时根本没办法优化SQL,因为它的SQL都是自动生成的,而这里如果是mybatis就大不一样了,像上面那条SQL,如果在查第一张表的时候就加上过滤条件,就不用查很多数据了,SQL的性能就能得到提升,所以这也算是mybatis相对hibernate一个很大的优势吧。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值