MyBatis的体系结构
SqlSessionFactory
SqlSessionFactory是MyBatis的关键对象,它是单个数据库映射关系经过编译后的内存镜像。
它是创建SqlSession的工厂类。
SqlSession
SqlSession是MyBatis的关键对象,是执行持久化操作的对象,类似于JDBC中的Connection。它是应用程序与持久存储层之间执行交互操作的一个单线程对象。
它的底层封装了JDBC连接。SqlSession的实例不能被共享,也是线程不安全的,决不能将SqlSession实例的引用放在一个类的静态字段甚至是实例字段中。所以,执行完后必须关闭,也应该使用finnally来关闭它。
它有如下方法:
<T> T selectOne(String statement, Object parameter)
<E> List<E> selectList(String statement, Object parameter)
<K,V> Map<K,V> selectMap(String statement, Object parameter, String mapKey)
int insert(String statement, Object parameter)
int update(String statement, Object parameter)
int delete(String statement, Object parameter)
深入MyBatis配置文件
通过上面的介绍,我们知道MyBatis持久化操作离不开SqlSessionFactory对象,这个对象是整个数据库映射关系经过编译后的内存镜像,该对象的openSession()方法可以打开SqlSession对象,该对象由SqlSessionFactoryBuilder加载MyBatis的配置文件生成。
我们看看这句话发生了什么:SqlSessionFactory sqlSessionFactory=new SqlSessionFactoryBuilder().build(inputStream);
SqlSessionFactory根据传入的数据流生成Configuration对象,然后根据Configuration对象创建默认的SqlSessionFactory实例。
MyBatis的配置文件结构
MyBatis的配置文件包含了影响MyBatis行为的信息。文档的结构如下:
settings设置:
typeAliases类型命名:
类型命名是用来减少冗余的限定名的。
<typeAliases>
<typeAlias alias="user" type="com.leesanghyuk.domain.User"/>
</typeAliases>
这样配置,user可以用在任何使用到这个类的地方。
<typeAliases>
<package name="com.leesanghyuk.domain"/>
</typeAliases>
这样在com.leesanghyuk.domain里的java bean,在没有注解的情况下,会使用Bean的首字母小写的非限定类名作为它的别名。比如com.leesanghyuk.domain.User的别名为user,若有注解,则别名为其注解值。
@Alias("user")
public class User{
...
}
可能不太清楚,具体请看:
https://blog.csdn.net/majinggogogo/article/details/71503263
typeHandlers类型处理器
这个标签的作用是用于处理类型的转化,比如你想把String类型的日期(如2018-11-01)转换为能够存储在数据库中Date类型,就需要转换。
详情查看:
https://blog.csdn.net/chenbaige/article/details/72568959
objectFactory对象工厂
MyBatis 每次创建结果对象的新实例时,它都会使用一个对象工厂(ObjectFactory)实例来完成。 默认的对象工厂需要做的仅仅是实例化目标类,要么通过默认构造方法,要么在参数映射存在的时候通过参数构造方法来实例化。 如果想覆盖对象工厂的默认行为,则可以通过创建自己的对象工厂来实现。对于像我这种水平的来说,完全就是一脸蒙逼,这到底有啥用?可以说这个对象工厂就是用来创建实体类的,MyBatis有一个DefaultObjectFactory默认对象工厂类,就像上面所说的默认的对象工厂需要做的仅仅是实例化目标类,要么通过默认构造方法,要么在参数映射存在的时候通过参数构造方法来实例化。它不做其他任何的处理。如果我们想在创建实例化一个目标类的时候做点啥其他的动作,可以继承DefaultObjectFactory,覆写父类方法,并在mybatis-config.xml中注册配置这个对象工厂类。
environments配置环境
略
mapper映射器
略
深入Mapper XML映射文件
select
他是用来映射查询语句的。例如:
<select id="selectUser" parameterType="int"
resultType="hashmap">
SELECT * FROM TB_USER WHERE ID= #{id}
</select>
它还有很多属性:
- id:被用来引用这条语句的标识符。
- parameterType:这条语句的参数类的完全限定名或者别名。
- resultType:从这条语句中返回的期望类型的类的完全限定名或别名。如果是集合,则使用resultType或resultMap,两者不可同时使用。
- resultMap:同上。
- flushCache:如果设置为true,则清空本地和二级缓存,默认为false。
- useCache:如果设置为true,则导致本语句的结果被二级缓存。默认为true.
- timeout:请求数据库的等待秒数,默认值为unset,依赖驱动。
- fetchSize:每次返回的结果行数与这个设置的值相同,默认为unset。
- statementType:值为STATEMENT/PREPARED/CALLABLE,这会让使用jdbc中的Statement、PreparedStatement、CallableStatement,默认为PREPARED。
- resultSetType:结果集的类型,值为FORWARD_ONLY、SCROLL_SENSITIVE、SCROLL_INSENSITIVE,默认值为unset。
- databaseId
- resultOrdered
- resultSets
insert、update、delete
- useGeneratedKeys:(仅对insert和update有用)这会获得数据库内部自动生成的主键(比如MySql里的自动递增字段),默认为false。
- keyProperty:http://bugyun.iteye.com/blog/2286145
- resultType
- order
- statementType
sql
${}
#{}
的区别
https://blog.csdn.net/lohannes/article/details/79031435
下面来具体写代码:
<?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.leesanghyuk.mapper.UserMapper">
<insert id="saveUser" parameterType="user"
useGeneratedKeys="true">
INSERT INTO TB_USER(name,sex,age) VALUES (#{name},#{sex},#{age})
</insert>
<select id="selectUser" parameterType="int" resultType="user">
SELECT * FROM TB_USER WHERE id=#{id}
</select>
<update id="modifyUser" parameterType="user">
UPDATE TB_USER
SET name=#{name},sex=#{sex},age=#{age}
WHERE id=#{id}
</update>
<delete id="removeUser" parameterType="int">
DELETE FROM TB_USER WHERE id=#{id}
</delete>
</mapper>
因为每次测试都要读取mybatis-config.xml文件,根据配置文件获取SqlSessionFactoty然后又获取SqlSession,该方法比较麻烦,所以开发一个工厂类FKSqlSessionFactory。
package com.leesanghyuk.tst;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.IOException;
import java.io.InputStream;
public class FKSqlSessionFactory {
private static SqlSessionFactory sqlSessionFactory=null;
//初始化创建SqlSessionFactory对象
static {
try {
InputStream inputStream= Resources.getResourceAsStream("mybatis-config.xml");
sqlSessionFactory=new SqlSessionFactoryBuilder().build(inputStream);
} catch (IOException e) {
e.printStackTrace();
}
}
//获取SqlSession对象的静态方法
public static SqlSession getSqlSession(){
return sqlSessionFactory.openSession();
}
//获取SqlSessionFactory的静态方法
public static SqlSessionFactory getSqlSessionFactory(){
return sqlSessionFactory;
}
}
然后测试:
package com.leesanghyuk.tst;
import com.leesanghyuk.model.User;
import org.apache.ibatis.session.SqlSession;
import java.io.IOException;
public class MyBatisTest {
public static void main(String[] args) throws IOException {
SqlSession sqlSession=FKSqlSessionFactory.getSqlSession();
User user=sqlSession.selectOne("com.leesanghyuk.mapper.UserMapper.selectUser",1);
user.setName("LeesangHyuk");
sqlSession.update("com.leesanghyuk.mapper.UserMapper.modifyUser",user);
sqlSession.commit();
sqlSession.close();
}
}
ResultMaps
往往,我们select的对象返回是一个集合。resultMap的作用就是取出的数据换为开发者需要的对象。
我们新增一个映射语句如下:
<select id="selectAllUsers" resultType="map">
SELECT * FROM TB_USER
</select>
代码如下:
public class MyBatisTest {
public static void main(String[] args) throws IOException {
SqlSession sqlSession=FKSqlSessionFactory.getSqlSession();
List<Map<String,Object>> list=sqlSession.selectList("com.leesanghyuk.mapper.UserMapper.selectAllUsers");
for (Map<String,Object> row:list){
System.out.println(row);
}
sqlSession.commit();
sqlSession.close();
}
}
输出如下:
可以看到,查询语句返回的每一条数据都被封装成一个Map集合,列名作为Map集合的key,而列值作为Map的value。
虽然,可以作为Map集合作为返回,但是我们怎么获取单个的值赋值给User呢?
for (Map<String,Object> row:list){
System.out.println(row);
System.out.println(row.get("name"));
}
输出如下:
很明显,我们这样取到的,这是单个的列以及单元的值。很不方便,我们需要另外一种方案,描述一个领域模型。
<resultMap id="userResultMap" type="user">
<id property="id" column="id"/>
<result property="name" column="name"/>
<result property="sex" column="sex"/>
<result property="age" column="age"/>
</resultMap>
<select id="selectAllUsers2" resultMap="userResultMap">
SELECT * FROM TB_USER
</select>
上面使用了新的元素<resultMap>
,属性包含id和type,很好理解。其元素如下:
<id>
:表示数据表的主键,其中column表示列名,property表示数据库列映射到返回类型的属性。<result>
:表示数据表的普通列。
测试代码如下:
public class MyBatisTest {
public static void main(String[] args) throws IOException {
SqlSession sqlSession=FKSqlSessionFactory.getSqlSession();
List<User> userList=sqlSession.selectList("com.leesanghyuk.mapper.UserMapper.selectAllUsers2");
for (User user: userList) {
System.out.println(user.getName());
}
sqlSession.commit();
sqlSession.close();
}
}
这种映射,即使列名和User对象的属性名不一致,数据依然会被正确封装。
在实际开发中,还有更复杂的情况。例如,执行的是一个多表查询语句,而返回的对象关联到另一个对象,此时,简单的映射已经无法解决问题,就必须使用<resultMap>
来完成映射。
我们来看看如下的例子:
我们重新创建两个表:TB_CLAZZ和TB_STUDENT
use mybatis;
create table `tb_clazz`(
`id` int primary key auto_increment,
`code` varchar(18)
);
insert into tb_clazz(code) values('j1601');
insert into tb_clazz(code) values('j1602');
create table `tb_student`(
`id` int primary key auto_increment,
`name` varchar(18),
`sex` char(3),
`age` int,
`clazz_id` int,
FOREIGN KEY (`clazz_id`) REFERENCES tb_clazz(`id`)
);
insert into tb_student(name,sex,age,clazz_id) values('java','男',22,1);
insert into tb_student(name,sex,age,clazz_id) values('json','女',18,1);
insert into tb_student(name,sex,age,clazz_id) values('c#','男',25,2);
insert into tb_student(name,sex,age,clazz_id) values('mybatis','女',20,2);
好了,以上的SQL语句插入了两个班级记录和4个学生记录,包含了外键。
现在创建一个Clazz对象和Student对象分别映射两表。
public class Clazz {
private Integer id;
private String code;
...
}
public class Student {
private Integer id;
private String name;
private String sex;
private Integer age;
//关联的Clazz
private Clazz clazz;
...
}
需要注意的是,这里使用的是Student中的Clazz对象并不是使用id对应的。
现在写一个新的StudentMapper.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="com.leesanghyuk.mapper.StudentMapper">
<resultMap id="studentResultMap" type="Student">
<id property="id" column="id"/>
<result property="name" column="name"/>
<result property="sex" column="sex"/>
<result property="age" column="age"/>
<!--关联映射-->
<association property="clazz" column="clazz_id"
javaType="Clazz" select="selectClazzWithId"/>
</resultMap>
<select id="selectClazzWithId" resultType="Clazz">
SELECT * FROM TB_CLAZZ WHERE id=#{id}
</select>
<select id="selectStudent" resultMap="studentResultMap">
SELECT * FROM TB_STUDENT
</select>
</mapper>
我们在mybatis-config里面加入新的typeAliases以及mapper即可:
...
<!--typeAliases-->
<typeAliases>
<typeAlias type="com.leesanghyuk.model.User" alias="user"/>
<package name="com.leesanghyuk.model"/>
</typeAliases>
...
<mappers>
<mapper resource="com/leesanghyuk/mapper/UserMapper.xml"/>
<mapper resource="com/leesanghyuk/mapper/StudentMapper.xml"/>
</mappers>
然后写个测试即可:
package com.leesanghyuk.tst;
import com.leesanghyuk.model.Student;
import org.apache.ibatis.session.SqlSession;
import java.util.List;
public class SelectStudentTest {
public static void main(String[] args) {
SqlSession sqlSession=FKSqlSessionFactory.getSqlSession();
List<Student> students=sqlSession.selectList("com.leesanghyuk.mapper.StudentMapper.selectStudent") ;
for (Student s : students) {
System.out.println(s.getName());
}
sqlSession.commit();
sqlSession.close();
}
}
ojbk~
我们来了解一下<association>
,除去之前解释过的column
和property
两个属性,还有:
- javaType:也就是该属性对应的类型名称。
- select:表示执行一条查询语句,并将查询到的数据封装到property所代表的类型对象当中。
结果如下:
新的难题出现了,假如我们是要打印班级的信息而不是学生的信息怎么办?我们知道,一个班级中会有多个学生存在,而一个学生只属于一个班级。但是我们的表tb_clazz并不存在外键映射到tb_student。
我们给Clazz.java新增一个字段:
public class Clazz {
private Integer id;
private String code;
private List<Student> students;
...
}
现在配置StudentMapper.xml
<!--映射班级对象的resultMap-->
<resultMap id="clazzResultMap" type="Clazz">
<id property="id" column="id"/>
<result property="code" column="code"/>
<collection property="students" column="id"
javaType="ArrayList" ofType="Student"
select="selectStudentWithId"/>
</resultMap>
<select id="selectStudentWithId" resultType="Student">
SELECT * FROM TB_STUDENT WHERE clazz_id=#{id}
</select>
<select id="selectClazz" resultMap="clazzResultMap">
SELECT * FROM TB_CLAZZ
</select>
首先来解释一下执行id为selectClazz的语句,因为Clazz不是一个简单的对象,它其中还包含了学生的集合对象,所以使用resultMap去映射返回类型。
<collection>
的元素解释如下:
- property:表示返回类Clazz对象的属性students
- column:表示使用这个列作为参数进行之后的select语句查询。
- javaType:表示该属性对应的类型名称。
- ofType:表示集合当中的类型,即集合为
ArrayList<Student>
- select:要执行的语句id
下面测试:
package com.leesanghyuk.tst;
import com.leesanghyuk.model.Clazz;
import com.leesanghyuk.model.Student;
import org.apache.ibatis.session.SqlSession;
import java.util.List;
public class SelectClazzTest {
public static void main(String[] args) {
SqlSession sqlSession=FKSqlSessionFactory.getSqlSession();
List<Clazz> clazzes=sqlSession.selectList("com.leesanghyuk.mapper.StudentMapper.selectClazz");
for (Clazz c:clazzes) {
System.out.println(c);
for (Student s:c.getStudents()) {
System.out.print(s.getName()+" "+c.getCode()+" ");
}
System.out.println();
}
sqlSession.commit();
sqlSession.close();
}
}
注意,由于这里Student又包含了Clazz,所以搜索出来的Student的Clazz属性是空。这个问题怎么解决呢?我们下一章继续讨论。