关于Mybatis
Mybatis
是一个比较流行的ORM
框架,准确地说是一个半自动的ORM
框架,sql
语句均需要自己手动编写(当然借助mybatis generator
插件也可以为我们生成一些sql
模版,但是框架本身是不提供的)。Mybatis
被推崇的原因,一是有人说他灵活,有时候自己手写的sql
语句查询效率更高(这点存疑,因为从实践看来大多数时候手写的sql
语句与一些全自动ORM
框架生成的效率相当,有时候甚至更低)。二是简单易用,基本只要会编写sql
语句都能快速上手。
使用Mybatis
- 环境搭建
使用IDEA
新建一个简单的Java
项目,然后从maven
官网上下载以下2
个jar
包到项目目录下,并添加为Library
。(jar
包具体版本根据自己情况而定,其中2
为mysql
的驱动)
- mybatis-3.4.6.jar
- 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
表进行CRUD
的sql
语句都写进这个xml
文件里
PersonMapper.xml
后面跟着namespace
定义了所有该mapper
下中的sql
语句的命名空间,且对每一个sql
语句都还有一个该namespace
下的唯一id
,因此我们可以使用namespace.id
的方式定位到一个sql
语句。对于select
、update
, insert
, delete
都有相应的xml
标签,我们在相应的标签下写对应的sql
语句即可。这个sql
语句很简单,根据id
从Person
表中返回一行记录,resultType
定义了返回结果的类型,注意这里要写全类名,
现在我们定义好了数据库Person
表对应的实体类,也在PersonMapper.xml
定义了在该表上的CRUD
操作。接下来我们要通过配置告诉Mybatis
两件事
- 如何连接数据库
- 我们之前定义的
PersonMapper.xml
具体位置
- 配置
Mybatis
在这个配置文件里,我们可以定义多个environment
,并设置一个默认的environment
,从而可以轻易的开发环境及测试环境进行切换。在
中则定义了我们之前编写的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
语句, 我们调用Session
的selectOne()
方法,该方法可以返回一条查询记录,之前我们在定义sql
语句时候制定了一个int
型的参数来指定id
,因此在这个方法中我们也要把这个参数传进去。
- 实验结果
Person{id=1, name='Tom', age=26}
可以看见mybatis
帮我们访问数据库,拿到了id=1
的记录并包装成了一个Person
类实例返回给我们。接下来我们编写除了select
以外的其他3
种数据库操作类型,来完善这个实验。
- PersonMapper.xml
INSERT INTO Person (id, name, age) VALUES (#{id}, #{name}, #{age})
DELETE FROM Person
WHERE id = #{id}
UPDATE Person SET
name = #{name},
age = #{age}
WHERE id = #{id}
id
=selectAllPerson
的查询语句返回Person
中的所有记录,这里注意返回值类型依然为Person
,代表的是每条记录的类型。在使用Mybatis
时,可以调用session
的selectList()
方法让其返回一个由Person
类型组成的列表。
List people = session.selectList("entity.PersonMapper.selectAllPerson");
id
=insertPerson
的操作表示插入一行记录,一行记录由一个Person
类实例表示,因此parameterType
为entity.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 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
中每个sql
的id
相同的方法,并且该方法的参数和返回值也和PersonMapper.xml
中的一致。例如以下的sql
语句对应了PersonMapper
类中的同名方法
- PersonMapper.xml
- PersonMapper.java
public interface PersonMapper {
Person selectPerson(int id);
}
id=selectPerson
的select
语句,对应了PersonMapper
接口的selectPerson
, parameterType="int"
对应了selectPerson
方法的int
型参数id
。resultType="entity.Person"
对应了该接口方法的返回值Person
。
基于这种方式,我们可以在接口中实现其他几条sql
语句对应的接口方法。
- 完整的
PersonMapper
接口
public interface PersonMapper {
Person selectPerson(int id);
List 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
让我们再次运行测试类,就会报错,因为该接口找不到对应的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 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
语句的时候,会利用#{}
这个语法来引用传递进来的参数
Mybatis
还提供了${}
语法,两者的差别整体上如下
-
- 对于基本数据类型和
String
,#{}
中可以写任意参数名,而${}
中的参数名需要指定为value
,也就是说上面的sql
语句,若用${}
方式替换,则如下
- 对于基本数据类型和
-
- 对于
String
类型的参数,${}
不会转译,而#{}
会自动为其添加``。例如如下的sql
语句
- 对于
当我们调用person = personMapper.selectByName("Ben");
Mybatis
为我们生成的sql
语句是这样的。
SELECT * FROM Person WHERE name = Ben
这显然是错的,我们想要的是WHERE name = 'Ben'
。
因此我们需要手动的在${value}
两侧加上单引号。
而如果使用#{value}
,则会自动就帮我们把单引号加上,那么为什么还要多此一举设计${}
呢?因为我们很多时候并不想Mybatis
自动帮我们转译。
比如我们设计一个sql
语句,当我们传入id
时,其将表中记录按照id
排序,传入age
时,其将表中记录按照age
排序。我们就可以这样写
-
- 对于其他数据类型,
${}
和#{}
等价,参数名都需要和传入参数的各个属性名相同。
- 对于其他数据类型,
结果映射ResultMap
之前创建Person
类的时候,Person
类中的每个属性名和Person
表中的字段都是一一对映的,因此当返回结果为enetity.Person
时,Mybatis
会自动帮我们把结果包装进对应的属性名里面,那么如果属性名和表中字段名不一致怎么办?比如我想把Person
表的id
字段映射到Person
类的Rank
字段。为了更加方便的解决这个问题,Mybatis
给我们提供了ResultMap
,让我们可以把自定义表中的某个列所关联的属性名。
- 定义
ResultMap
这里我们定义了一个id
为idToRank
的resultMap
,type
表示这个映射的类型,我们是要把Person
表的字段映射到Person
类中,因此返回的类型是entity.Person
,接下来就是定义属性和表中的列的映射了,这里要注意,如果该列是主键,则要单独处理,用
这个标签来定义。
- 使用
ResultMap
使用时只要把resultType
修改为resultMap
,后接对映的resultMap
的id
即可。
利用Map
作为参数和返回值
之前说过Mybatis
里xml
中各个CURD
语句的传入的参数只能有一个,当我们要更新Person
表中多列时,会把各个列的值包装成一个Person
类作为parameterType
。除此之外,更方便的是利用Java
中的Map
对象作为parameterType
, 列名作为key
,对映的值作为value
。
在PersonMapper.xml
里添加如下语句。
指明了parameterType="Map"
, 然后Mybaits
就会获取这个Map
里的key=id
的值替换掉占位符#{id}
里的id
。
在PersonMapper
中添加对映方法,并测试它。
- PersonMapper.java
Person selectWithMap(Map params);
这里约定,Map
的泛型中key
为String
、value
为Object
。
- 测试
try(SqlSession session = sqlSessionFactory.openSession()){
PersonMapper personMapper = session.getMapper(PersonMapper.class);
Map 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
类型的。
- PersonMapper.java
- Test.java
try(SqlSession session = sqlSessionFactory.openSession()){
PersonMapper personMapper = session.getMapper(PersonMapper.class);
Map map = personMapper.selectPerson(1);
for (String k : map.keySet()) {
System.out.println(k+ " " + map.get(k));
}
}
- 测试结果
id1 1
name TOM
age 26