Mybatis使用入门

关于Mybatis

Mybatis是一个比较流行的ORM框架,准确地说是一个半自动ORM框架,sql语句均需要自己手动编写(当然借助mybatis generator插件也可以为我们生成一些sql模版,但是框架本身是不提供的)。Mybatis被推崇的原因,一是有人说他灵活,有时候自己手写的sql语句查询效率更高(这点存疑,因为从实践看来大多数时候手写的sql语句与一些全自动ORM框架生成的效率相当,有时候甚至更低)。二是简单易用,基本只要会编写sql语句都能快速上手。

使用Mybatis

  • 环境搭建
    使用IDEA新建一个简单的Java项目,然后从maven官网上下载以下2jar包到项目目录下,并添加为Library。(jar包具体版本根据自己情况而定,其中2mysql的驱动)
  1. mybatis-3.4.6.jar
  2. mysql-connector-java-8.0.15.jar

然后再在mysql中新建一个数据库mybatisStudy,在这个数据库中新建一张Person

CREATE DATABASE mybatisStudy;
CREATE TABLE Person(
    id INT NOT NULL,
    name VARCHAR(20),
    age INT,
    PRIMARY KEY(id)
    )ENGINE=InnoDB DEFAULT CHARSET=utf8;

再表中插入一行演示数据

INSERT INTO Person(id, name, age) VALUES(1, 'Tom', '26');
SELECT * FROM Person;
+----+------+------+
| id | name | age  |
+----+------+------+
|  1 | Tom  |   26 |
+----+------+------+
1 row in set (0.00 sec)

我们知道ORM是为了将数据库某个表中的一行数据映射成一个Java对象,当我们想要对该行数据库中的某一行记录进行修改时,只要操作代表着该行记录的Java对象就可以了,简单说就是把对数据库表的操作转换为对Java对象的操作。所以我们要为Person表建立一个对应的Person类,一个Person类的实例就是Person表中的一行记录。

  • Person
public class Person {
    private int id;
    private String name;
    private int age;

    public Person(){};

    public Person(int id, String name, int age) {
        this.id = id;
        this.name = name;
        this.age = age;
    }

    public int getId() {
        return id;
    }

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

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

之前我们说到,Mybatis是一个半自动的ORM框架,需要我们自行编写Sql语句,那我们要在哪里进行Sql语句的编写呢?答案是在xml文件中,让我们创建一个名为PersonMapper.xml的配置文件,按照mybatis的格式,把对Person表进行CRUDsql语句都写进这个xml文件里

  • PersonMapper.xml
<?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="entity.PersonMapper">
    <select id="selectPerson" resultType="entity.Person" parameterType="int" >
        SELECT *
        FROM Person
        WHERE id = #{id}
    </select>
</mapper>

<mapper>后面跟着namespace定义了所有该mapper下中的sql语句的命名空间,且对每一个sql语句都还有一个该namespace下的唯一id,因此我们可以使用namespace.id的方式定位到一个sql语句。对于selectupdate, insertdelete都有相应的xml标签,我们在相应的标签下写对应的sql语句即可。这个sql语句很简单,根据idPerson表中返回一行记录,resultType定义了返回结果的类型,注意这里要写全类名,
现在我们定义好了数据库Person表对应的实体类,也在PersonMapper.xml定义了在该表上的CRUD操作。接下来我们要通过配置告诉Mybatis两件事

  1. 如何连接数据库
  2. 我们之前定义的PersonMapper.xml具体位置
  • 配置Mybatis
<?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://localhost:3306/mybatisStudy"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="entity/PersonMapper.xml"/>
    </mappers>
</configuration>

在这个配置文件里,我们可以定义多个environment,并设置一个默认的environment,从而可以轻易的开发环境及测试环境进行切换。在<mappers>中则定义了我们之前编写的PersonMapper.xml的具体位置。现在我们已经完成了所有准备工作,接下来可以通过Mybatis来对数据库操作了。

  • Test类
public class Test {
    public static void main(String[] args) throws Exception {
        String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

        try (SqlSession session = sqlSessionFactory.openSession()) {
            Person person = session.selectOne("entity.PersonMapper.selectPerson",1);
            System.out.println(person);
        }
    }
}

我们知道要对数据库进行操作,就需要和数据库建立一条连接,Mybatis给我们提供了一个工厂类,SqlSessionFactoryBuilder,每次调用该工厂类的openSession()的方法就会返回给我们一个已经与数据库建立好的连接,接着我们在这个session内执行sql语句操作数据库了,之前我们说了namespace.id可以定位到唯一的一条sql语句, 我们调用SessionselectOne()方法,该方法可以返回一条查询记录,之前我们在定义sql语句时候制定了一个int型的参数来指定id,因此在这个方法中我们也要把这个参数传进去。

  • 实验结果
Person{id=1, name='Tom', age=26}

可以看见mybatis帮我们访问数据库,拿到了id=1的记录并包装成了一个Person类实例返回给我们。接下来我们编写除了select以外的其他3种数据库操作类型,来完善这个实验。

  • PersonMapper.xml
<?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="entity.PersonMapper">
<select id="selectPerson" resultType="entity.Person" parameterType="int" >
        SELECT *
        FROM Person
        WHERE id = #{id}
    </select>

<select id="selectAllPerson" resultType="entity.Person">
        SELECT *
        FROM Person
    </select>

<insert id="insertPerson" parameterType="entity.Person">
        INSERT INTO Person (id, name, age) VALUES (#{id}, #{name}, #{age})
    </insert>

<delete id="deletePersonById" parameterType="int">
        DELETE FROM Person
        WHERE id = #{id}
    </delete>

<update id="updatePerson" parameterType="entity.Person">
        UPDATE Person SET
        name = #{name},
        age  = #{age}
        WHERE id = #{id}
    </update>

</mapper>

id=selectAllPerson的查询语句返回Person中的所有记录,这里注意返回值类型依然为Person,代表的是每条记录的类型。在使用Mybatis时,可以调用sessionselectList()方法让其返回一个由Person类型组成的列表。

List<Person> people = session.selectList("entity.PersonMapper.selectAllPerson");

id=insertPerson的操作表示插入一行记录,一行记录由一个Person类实例表示,因此parameterTypeentity.Person。注意inset,delete,update操作都没有返回值。
id=deletePersonById的操作表示根据id删除对应的一行记录。
id=updatePerson表示根据id,更行一行记录的信息,注意mybatis中,我们至多只能传入一个参数,因此要把更新的信息打包成一个Person实例传入,所以parameterType=entity.Person

  • 更新Test类
public class Test {
   public static void main(String[] args) throws Exception {
       String resource = "mybatis-config.xml";
       InputStream inputStream = Resources.getResourceAsStream(resource);
       SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

       try (SqlSession session = sqlSessionFactory.openSession()) {
           Person person = session.selectOne("entity.PersonMapper.selectPerson",1);
           System.out.println(person);

           session.insert("entity.PersonMapper.insertPerson", new Person(2,"Ben",41));
           session.commit();
           List<Person> people = session.selectList("entity.PersonMapper.selectAllPerson");
           System.out.println(people.toString());

           session.update("entity.PersonMapper.updatePerson", new Person(1, "jack", 47));
           session.commit();
           person = session.selectOne("entity.PersonMapper.selectPerson",1);
           System.out.println(person);

           session.delete("entity.PersonMapper.deletePersonById", 1);
           session.commit();
           people = session.selectList("entity.PersonMapper.selectAllPerson");
           System.out.println(people.toString());

       }
   }
}

  • 实验结果
Person{id=1, name='Tom', age=26}
[Person{id=1, name='Tom', age=26}, Person{id=2, name='Ben', age=41}]
Person{id=1, name='jack', age=47}
[Person{id=2, name='Ben', age=41}]

Process finished with exit code 0

接口搭配动态代理的方式使用Mybatis

前面我们在调用session的相关方法时,会为其指定一个sql语句,这样的方式可行,但是却容易出错,因此才实际使用中我们并不会采用这样的方法,而是采用接口搭配动态代理的方式,简单的讲,对于PersonMapper.xml文件,我们会创建和其同名的PersonMapper接口。

项目结构

且在这个接口中,我们会定义和PersonMapper.xml中每个sqlid相同的方法,并且该方法的参数和返回值也和PersonMapper.xml中的一致。例如以下的sql语句对应了PersonMapper类中的同名方法

  • PersonMapper.xml
<select id="selectPerson" resultType="entity.Person" parameterType="int" >
        SELECT *
        FROM Person
        WHERE id = #{id}
</select>
  • PersonMapper.java
public interface PersonMapper {
    Person selectPerson(int id);
}

id=selectPersonselect语句,对应了PersonMapper接口的selectPersonparameterType="int" 对应了selectPerson方法的int型参数idresultType="entity.Person"对应了该接口方法的返回值Person
基于这种方式,我们可以在接口中实现其他几条sql语句对应的接口方法。

  • 完整的PersonMapper接口
public interface PersonMapper {
    Person selectPerson(int id);
    List<Person> selectAllPerson();
    void insertPerson(Person person);
    void deletePersonById(int id);
    void updatePerson(Person person);
}

这里还有一个细节,PersonMapper类我们并不需要去实现它,Mybatis会使用动态代理的方式生成一个实现类,我们知道具体的sql语句是写在PersonMapper.xml,那么Mybatis是如何根据接口知晓我们PersonMapper.xml在哪里呢?实际上这里是约定优于配置的典型场景,回顾之前的项目结构,发现PersonMapper.java的全类名entity.PersonMapper和这前PersonMapper.xml中定义的namespace=entity.PersonMapper恰好相同,实际上Mybatis会自动的根据entity.PersonMapper这个类的全类名。去找namespace=entity.PersonMapper的映射器,在我们当前的配置中,2者恰好相同,所以没有报错,我们可以手动修改namespace来做个实验。

  • PersonMapper.xml
<mapper namespace="entity.tmp.PersonMapper">

让我们再次运行测试类,就会报错,因为该接口找不到对应的namespace相同的映射器。

Exception in thread "main" org.apache.ibatis.binding.BindingException: Type interface entity.PersonMapper is not known to the MapperRegistry.

有了这个接口后,我们可以让Mybatis给我们生成一个对应的实现类,当我们相执行id=selectPerson这个sql语句的时候,只要调用实现类的selectPerson方法就好了。

  • Test类
public class Test {
    public static void main(String[] args) throws Exception {
        String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

        try(SqlSession session = sqlSessionFactory.openSession()){
            PersonMapper personMapper = session.getMapper(PersonMapper.class);
            Person person = personMapper.selectPerson(1);
            System.out.println(person);

            List<Person> people = personMapper.selectAllPerson();
            System.out.println(people);
        }
    }
}

  • 实验结果
Person{id=1, name='TOM', age=26}
[Person{id=1, name='TOM', age=26}, Person{id=2, name='Ben', age=41}]

Process finished with exit code 0

#{ } 与 ${ }

之前在写sql语句的时候,会利用#{}这个语法来引用传递进来的参数

    <select id="selectPerson" resultType="entity.Person" parameterType="int" >
        SELECT *
        FROM Person
        WHERE id = #{id}
    </select>

Mybatis还提供了${}语法,两者的差别整体上如下

    1. 对于基本数据类型和String, #{}中可以写任意参数名,而${}中的参数名需要指定为value,也就是说上面的sql语句,若用${}方式替换,则如下
    <select id="selectPerson" resultType="entity.Person" parameterType="int" >
        SELECT *
        FROM Person
        WHERE id = ${value}
    </select>
    1. 对于String类型的参数,${}不会转译,而#{}会自动为其添加``。例如如下的sql语句
    <select id="selectByName" resultType="entity.Person" parameterType="String">
        SELECT *
        FROM Person
        WHERE name = ${value}
    </select>

当我们调用person = personMapper.selectByName("Ben");
Mybatis为我们生成的sql语句是这样的。

SELECT * FROM Person WHERE name = Ben

这显然是错的,我们想要的是WHERE name = 'Ben'
因此我们需要手动的在${value}两侧加上单引号。

    <select id="selectByName" resultType="entity.Person" parameterType="String">
        SELECT *
        FROM Person
        WHERE name = `${value}`
    </select>

而如果使用#{value},则会自动就帮我们把单引号加上,那么为什么还要多此一举设计${}呢?因为我们很多时候并不想Mybatis自动帮我们转译。
比如我们设计一个sql语句,当我们传入id时,其将表中记录按照id排序,传入age时,其将表中记录按照age排序。我们就可以这样写

    <select id="selectOrderByColumn" resultType="entity.Person" parameterType="String">
        SELECT *
        FROM Person
        ORDER BY ${value}
    </select>
    1. 对于其他数据类型,${}#{}等价,参数名都需要和传入参数的各个属性名相同。

结果映射ResultMap

之前创建Person类的时候,Person类中的每个属性名和Person表中的字段都是一一对映的,因此当返回结果为enetity.Person时,Mybatis会自动帮我们把结果包装进对应的属性名里面,那么如果属性名和表中字段名不一致怎么办?比如我想把Person表的id字段映射到Person类的Rank字段。为了更加方便的解决这个问题,Mybatis给我们提供了ResultMap,让我们可以把自定义表中的某个列所关联的属性名。

  • 定义ResultMap
    <resultMap id="idToRank" type="entity.Person">
        <id property="rank" column="id" />
        <result property="name" column="name"/>
        <result property="age" column="age"/>
    </resultMap>

这里我们定义了一个ididToRankresultMaptype表示这个映射的类型,我们是要把Person表的字段映射到Person类中,因此返回的类型是entity.Person,接下来就是定义属性和表中的列的映射了,这里要注意,如果该列是主键,则要单独处理,用<id>这个标签来定义。

  • 使用ResultMap
    <select id="selectPerson" resultMap="idToRank" parameterType="int" >
        SELECT *
        FROM Person
        WHERE id = ${value}
    </select>

使用时只要把resultType修改为resultMap,后接对映的resultMapid即可。

利用Map作为参数和返回值

之前说过Mybatisxml中各个CURD语句的传入的参数只能有一个,当我们要更新Person表中多列时,会把各个列的值包装成一个Person类作为parameterType。除此之外,更方便的是利用Java中的Map对象作为parameterType, 列名作为key,对映的值作为value
PersonMapper.xml里添加如下语句。

    <select id="selectWithMap" resultType="entity.Person" parameterType="Map">
          SELECT *
          FROM Person
          WHERE id = #{id} AND name = #{name}
    </select>

指明了parameterType="Map", 然后Mybaits就会获取这个Map里的key=id的值替换掉占位符#{id}里的id
PersonMapper中添加对映方法,并测试它。

  • PersonMapper.java
 Person selectWithMap(Map<String, Object> params);

这里约定,Map的泛型中keyStringvalueObject

  • 测试
try(SqlSession session = sqlSessionFactory.openSession()){
            PersonMapper personMapper = session.getMapper(PersonMapper.class);

            Map<String, Object> map = new HashMap<>();
            map.put("id", 1);
            map.put("name", "TOM");
}
  • 输出结果
Person{id=1, rank=0, name='TOM', age=26}

Map除了可以作为parameterType还可以作为resultType, 当作为resultType时,Mybatis会自动把值存进与列名相同的key,但是如果在sql语句中给一列取了别名,则会以该别名为key,返回的Map同样是Map<String, Object>类型的。

  • PersonMapper.java
    <select id="selectPerson" resultType="HashMap" parameterType="int" >
        SELECT id as id1, age, name
        FROM Person
        WHERE id = ${value}
    </select>
  • Test.java
try(SqlSession session = sqlSessionFactory.openSession()){
            PersonMapper personMapper = session.getMapper(PersonMapper.class);
            Map<String, Object> map = personMapper.selectPerson(1);
            for (String k : map.keySet()) {
                System.out.println(k+ " " + map.get(k));
            }
}
  • 测试结果
id1 1
name TOM
age 26
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,753评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,668评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,090评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,010评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,054评论 6 395
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,806评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,484评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,380评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,873评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,021评论 3 338
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,158评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,838评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,499评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,044评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,159评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,449评论 3 374
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,136评论 2 356

推荐阅读更多精彩内容