Mybatis详解

Mybatis详解

原文地址:Mybatis详解

写在最前

个人的学习笔记,如果有错误的地方,请各位大佬指出orz

概述

Mybatis是一个优秀的基于Java的持久层框架,它内部封装了Jdbc,使开发者只需要关注sql语句本身,而不需要花费精力去处理加载驱动、创建连接、创建statement等繁杂的过程。

Mybatis通过xml或注解的方式将要执行的各种statement配置起来,并通过java对象和statement中sql的动态参数进行映射生成最终执行的sql语句,最后由Mybatis框架执行sql并将结果映射为java对象并返回。

采用ORM思想解决了实体和数据库映射的问题,对Jdbc进行了封装,屏蔽了Jdbc Api底层访问细节,使我们不用与Jdbc Api打交道,就可以完成对数据库的持久化操作。

Mybatis开源

https://github.com/mybatis/mybatis-3

Mybatis文档

https://mybatis.org/mybatis-3/zh/index.html

入门

Mybatis需要两个配置,一个是关于总体的配置,一个是具体的每个Dao的映射配置。下面用一个简单的Demo来演示下,具体的参数含义后面再说,先总体上感受一下。

总配置:SqlMapConfig.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>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://127.0.0.1:3306/chayedan_mybatis?characterEncoding=utf8"/>
                <property name="username" value="root"/>
                <property name="password" value="123"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="UserMapper.xml"></mapper>
    </mappers>
</configuration>

映射配置:

<?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="user">
    <select id="findAll" resultType="com.chayedan.domain.User">
        select * from user
    </select>
</mapper>

User类

package com.chayedan.domain;

import java.util.Date;

public class User {
    private int id;
    private String username;
    private String gender;
    private Date birthday;
    private String address;
    private String order;

    public String getOrder() {
        return order;
    }

    public void setOrder(String order) {
        this.order = order;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getGender() {
        return gender;
    }

    public void setGender(String gender) {
        this.gender = gender;
    }

    public Date getBirthday() {
        return birthday;
    }

    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", username='" + username + '\'' +
                ", gender='" + gender + '\'' +
                ", birthday=" + birthday +
                ", address='" + address + '\'' +
                '}';
    }
}

测试类

public class AppTest {
    @Test
    public void testFindAll() {
        //创建一个sqlSessionFactoryBuilder
        SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
        //创建sqlSessionFactory
        SqlSessionFactory sqlSessionFactory = builder.build(AppTest.class.getClassLoader().getResourceAsStream("SqlMapConfig.xml"));

        //创建sqlSession
        SqlSession sqlSession = sqlSessionFactory.openSession();

        //执行 sql语句
        List<User> list = sqlSession.selectList("user.findAll");

        list.forEach(u-> System.out.println(u));

    }
}

映射配置中的mapper标签的命名空间需要注意下

<mapper namespace="user">

跟在测试类中的的selectList中的参数是对应的

List<User> list = sqlSession.selectList("user.findAll");

意思就是将命名空间为user的mapper中的id为findAll的标签传递过去

CRUD

查询

上面的入门例子已经演示过了,下面再演示一个

<select id="findById" parameterType="int" resultType="com.chayedan.domain.User">
    select * from user where id=#{id}
</select>
User user = sqlSession.selectOne("user.findById", 2);

比较特殊点的排序例子

<select id="findByLike1" parameterType="com.chayedan.domain.User" resultType="com.chayedan.domain.User">
    select * from user where username like ${username} order by ${order} desc
</select>
SqlSession sqlSession = sqlSessionFactory.openSession();
User user = new User();
user.setUsername("'%王%'");
user.setOrder("id");
List<User> list = sqlSession.selectList("user.findByLike1", user);

主要注意的就是#{}和${}的区别,详情请见备注


查询有两个常用的方法

selectList

selectOne:也是用selectList实现的,就是在返回的结果那做了一个判断,结果数大于一就报错。。。

增加

<insert id="save" parameterType="com.chayedan.domain.User"  >
    <!--
    last_insert_id() 用来获取此次插入的自增长id 的值
    keyProperty:用来表示插入的id的值 赋值给对象的哪个属性
	order:是在下面的语句之后还是之前执行
    -->
    <selectKey resultType="int" keyProperty="id" order="AFTER">
        select last_insert_id()
    </selectKey>
    insert into user VALUES (null,#{username},#{gender},#{birthday},#{address})
</insert>
User user = new User();
user.setUsername("小王");
user.setAddress("北京");
user.setBirthday(new Date());
user.setGender("1");

int insert = sqlSession.insert("user.save", user);
System.out.println("影响的行数"+insert);
System.out.println(user);
sqlSession.commit();
sqlSession.close();

insert方法,返回值为成功插入的行数,需要注意的是,Mybatis自动提交是默认关闭的

所以需要sqlSession.commit();来手动提交

另外select last_insert_id()是最后一次插入后ID的属性值,通过keyProperty,已经将ID属性赋值到传过来的user对象中了,通过System.out.println(user);就能看到最后一次插入的user对象的ID属性

更改

<update id="updateUsernameById" parameterType="com.chayedan.domain.User">
  update user set username=#{username} where id=#{id}
</update>
User user = new User();
user.setUsername("小蓝");
user.setId(9);

int i = sqlSession.insert("user.updateUsernameById", user);

sqlSession.commit();
sqlSession.close();

删除

<delete id="delById" parameterType="int" >
    DELETE from user where id=#{id}
</delete>
sqlSession.delete("user.delById",9);

sqlSession.commit();
sqlSession.close();

备注

#{}和${}的问题
  • #{}

    #{}等同于PreparedStatement中的占位符?,会自动对传入的字符串数据加一对单引号,可以避免Sql注入。比如 select * from user where username = #{username} ,传入的username为小张,那么最后打印出来的就是select * from user where username = ‘小张’

    #{}可以接收简单类型值或Pojo属性值。 如果parameterType传输单个简单类型值,#{}括号中可以是任意名称。

  • ${}

    ${}将传入的数据直接显示生成在Sql中,只是简单的拼接。如:order by i d , 如 果 传 入 的 值 是 i d , 则 解 析 成 的 S q l 为 o r d e r b y i d 。 如 果 上 面 的 例 子 使 用 {id},如果传入的值是id,则解析成的Sql为order by id。如果上面的例子使用 ididSqlorderbyid使{},则成了select * from user where username = 小张

真正的开始

写了上面一堆,是不是发现没什么卵用。还不是要自己写实现(AppTest里面的前面一堆)

有一说一,确实从刚才看来没什么用

但是

刚才只是铺垫,现在来真正的开始使用Mybatis


既然有大量的重复代码,而我们的核心是SQL语句和查询返回的结果,那么自然,我们就想到了动态代理

Mybatis使用动态代理技术,来让我们面向接口编程

DEMO

这是UserDao接口

public interface UserDao {
    User findById(int id);
    List<User> findAll();
}

这是配置文件

<?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="com.chayedan.dao.UserDao">
    <select id="findAll" resultType="com.chayedan.domain.User">
    	select * from user
    </select>
    <select id="findById" parameterType="int" resultType="com.chayedan.domain.User">
        select * from user where id=#{id}
    </select>
</mapper>   

测试类

@Test
    public void testFindById() {
        SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();

        sqlSessionFactory = builder.build(AppTest.class.getClassLoader().getResourceAsStream("SqlMapConfig.xml"));
        SqlSession sqlSession = sqlSessionFactory.openSession();
        //调用getMapper 可以获取一个该接口的实现类对象 底层采用动态代理技术
        
        UserDao userDao = sqlSession.getMapper(UserDao.class);

        User user = userDao.findById(1);
        System.out.println(user);
        sqlSession.close();
    }

这样Mybatis就自动地帮你完成了一个实现类,通过使用getMapper方法

不过需要注意的是,这种技术必须保证四个一样

  • 必须保证接口的类的全限定名,跟配置文件中的 namespace一样
  • 方法名 必须跟配置文件中 id一致
  • 参数必须配置声明参数一样
  • 返回必须跟配置文件声明一样

核心配置文件的相关属性

在入门中的那个配置文件其实已经差不多了,下面的都是一些有可能会用到的属性

typeAlias(别名)

如果嫌全限定名麻烦,那么可以使用别名的方式,有两种

方式一

<typeAliases>
    <typeAlias type="com.chayedan.domain.User" alias="user"></typeAlias>
</typeAliases>

这种就是你可以在resultType的地方使用user来代理全限定名

方式二

<typeAliases>
    <!--<typeAlias type="com.chayedan.domain.User" alias="user"></typeAlias>-->
    <package name="com.chayedan.domain"></package>
</typeAliases>

如果使用的是package这种方式,它会扫描包下的所有实体类,每个实体类的别名都是类名的小写形势。

Mapper配置文件的相关属性

在入门中的那个配置文件其实也已经差不多了,下面的都是一些有可能会用到的属性

resultMap(结果集映射)

<!--
    结果集映射:
    id: 为了结果映射起个id
    type: 为了哪个实体类
-->
<resultMap id="userMap" type="user">
    <!--
        property 声明jaavBean属性名
        column 声明数据库查询出来的结果集对应列名 (考虑别名)
        -->
    <result property="id" column="uid"></result>
    <result property="username" column="name"></result>
    <result property="gender" column="gender"></result>
    <result property="birthday" column="birthday"></result>
    <result property="address" column="address"></result>
</resultMap>
<select id="findById1" parameterType="int" resultMap="userMap">
    select id AS uid,NAME,gender,birthday,address  from user where id=#{id}
</select>

也就是将查出来列属性按规定映射到实体类上面

type就是对应的实体类,这里写user是因为前面核心配置中启用了别名。

最后将resultType改为resultMap,使用resultMap的id指定结果集映射的形式

传递多个参数的方法(parameterType)

将参数封装为JavaBean对象进行多个参数的传递

<select id="findByUsernameAndAddress" parameterType="user" resultMap="userMap">
    select id AS uid,NAME,gender,birthday,address  from user where name=#{username} AND address=#{address}
</select>

如果是复杂对象,可以用.来使用复合对象中的属性

<select id="findByCondition2" parameterType="productQuery" resultType="product">
    SELECT * FROM product WHERE cid=(
    SELECT cid FROM category WHERE cname=#{category.cname}
    )
    AND cicun=#{product.size}
</select>

ProductQuery类中包含了Product类和Category类

Product类中有size属性

Category类中有cname属性

if标签和where标签(传递不定长参数)

跟Java中的if一样,test是必须的,等号后面的字符串是判断条件

<select id="findByCondition"  parameterType="user" resultMap="UserMap">
    select * from user where 1=1
        <if test="username!=null">
            and name=#{username}
        </if>
        <if test="address!=null">
            and address=#{address}
        </if>
    </where>
</select>

where标签则是整理where里的条件,可以帮助去掉第一个and,这样不需要 where 1=1

<select id="findByCondition"  parameterType="user" resultMap="UserMap">
    select * from user
    <where>
        <if test="username!=null">
            and name=#{username}
        </if>
        <if test="address!=null">
            and address=#{address}
        </if>
    </where>
</select>

foreach标签

如果根据多个id查询用户信息,就需要传递一个集合进去拼接sql语句,这时候就需要用到foreach标签。

为了好解释,写一段Java代码对应

list list=new arraylist();
list.add(1);
list.add(2);
list.add(4);
list.add(6);
list.add(9);

String sql=" select * from user where"
sql+=" id in( "
for(int i:list){
sql+=i+","
}
sql=sql.substring(0,sql.length()-1);
sql+=")"

xml中的写法

<select id="findByIds1"  parameterType="list" resultMap="UserMap">
    select * from user
    <where>
        <foreach collection="list" separator="," item="x" close=")" open="id in(">
            #{x}
        </foreach>
    </where>
</select>

collection:指明你遍历是什么属性

  • list集合——list
  • 数组——array
  • 复合查询对象——属性名

separator:分隔符,即java代码中的11行中的那个加号后面的逗号

item:遍历项,即java代码中第十行的int i的i,跟java一样,随便叫什么都可以

close:结束添加项,即最后你要拼接上什么,即第十四行

open:开始添加项,即拼接开始前需要以什么开头,即第九行

下面是复合查询的例子

复合查询类(主要是属性名一定要对应上)

public class QueryObject {
    private List<Integer> ids;
    private String address;

    public List<Integer> getIds() {
        return ids;
    }

    public void setIds(List<Integer> ids) {
        this.ids = ids;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }
}

xml文件的写法

<select id="findByIdsAndAddress"  parameterType="queryObject" resultMap="UserMap">
    select * from user
    <where>
        <foreach collection="ids" separator="," item="x" close=")" open="id in(">
            #{x}
        </foreach>
        and address=#{address}
    </where>
</select>

collection="ids"中的ids要和类中的private List ids 属性名对应

注意:这里写parameterType="queryObject"是因为queryObject我已经用了别名,如果没有用别名,需要写全限定名

association标签

使用在多表查询时,两个表的查询对象是一对一关系的数据

<resultMap id="userWithInfo" type="user">
    <result property="id" column="id"></result>
    <result property="username" column="name"></result>
    <result property="gender" column="gender"></result>
    <result property="birthday" column="birthday"></result>
    <result property="address" column="address"></result>
    <association property="userInfo" javaType="userInfo">
        <result property="uid" column="uid"></result>
        <result property="hobbies" column="hobbies"></result>
        <result property="email" column="email"></result>
    </association>

</resultMap>

<select id="findByIdWithInfo" parameterType="int" resultMap="userWithInfo">

    SELECT * FROM USER u,userinfo ui WHERE u.`id`=ui.`uid` AND u.id=#{id}

</select>

association:剩下部分数据与主体的关系

property:关联的属性名,对应下面的变量名userInfo

javaType:关联的属性类型,对应下面的类名UserInfo

public class User {
    private int id;
    private String username;
    private String gender;
    private Date birthday;
    private String address;
    private String order;
    private UserInfo userInfo;
}

collection标签

使用在多表查询时,两个表的查询对象是一对多关系或者多对多的关系时

<resultMap id="userWithOrders" type="user">
    <result property="id" column="id"></result>
    <result property="username" column="name"></result>
    <result property="gender" column="gender"></result>
    <result property="birthday" column="birthday"></result>
    <result property="address" column="address"></result>

    <collection property="orders" ofType="order">
        <result property="oid" column="oid"></result>
        <result property="desc" column="desc"></result>
        <result property="price" column="price"></result>
        <result property="createTime" column="createTime"></result>
        <result property="uid" column="uid"></result>
    </collection>
</resultMap>

collection:数据与主体关联关系

property:主体中属性名,即 collection 标签的property对应下面的oeders。

ofType:集合中的对象的类型,即 collection 标签的ofType对应下面的Order。

private List<Order> orders;

sql标签

统一定义字段的,因为在开发中最好不要用select * ,需要自己定义字段名

<sql id="userFields">
    id,username,gender,birthday,address
</sql>

相当于userFields=id,username,gender,birthday,address

那么就可以在其他标签中使用

<select id="findAll" resultType="com.chayedan.domain.User">
    select
    <include refid="userFields"/>
    from user
</select>

跟以前的作用是一样的

如果要引入其他Mapper的sql标签,可以使用全限定名,不过还不如在上面自己再写一个sql标签,免得写这么长的全限定名

<select id="findUsersWithOrder" resultMap="userWithOrders">      
    SELECT
    <include refid="userFields"/>,
    <include refid="com.chayedan.dao.OrderDao.orderFields"/>
    FROM USER u LEFT JOIN orders o ON u.id=o.uid
</select>
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值