MyBatis的基本应用
它是一款半自动的ORM持久层框架,具有较高的SQL灵活性,支持高级映射(一对一,一对多),动态SQL,延迟加载和缓存等特性,但它的数据库无关性较低。
- ORM
- Object Relation Mapping,对象关系映射。对象指的是Java对象,关系指的是数据库中的关系模型,对象关系映射,指的就是在Java对象和数据库的关系模型之间建立一种对应关系,比如用一个Java的Student类,去对应数据库中的一张student表,类中的属性和表中的列一一对应。Student类就对应student表,一个Student对象就对应student表中的一行数据
- 为什么mybatis是半自动的ORM框架?
- 用mybatis进行开发,需要手动编写SQL语句。而全自动的ORM框架,如hibernate,则不需要编写SQL语句。用hibernate开发,只需要定义好ORM映射关系,就可以直接进行CRUD操作了。由于mybatis需要手写SQL语句,所以它有较高的灵活性,可以根据需要,自由地对SQL进行定制,也因为要手写SQL,当要切换数据库时,SQL语句可能就要重写,因为不同的数据库有不同的方言(Dialect),所以mybatis的数据库无关性低。虽然mybatis需要手写SQL,但相比JDBC,它提供了输入映射和输出映射,可以很方便地进行SQL参数设置,以及结果集封装。并且还提供了关联查询和动态SQL等功能,极大地提升了开发的效率。并且它的学习成本也比hibernate低很多
快速入门
创建数据库
添加依赖
<!--mysql连接驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.10</version>
</dependency>
<!--mybatis-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.6</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.10</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>RELEASE</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<!--分页插件-->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.1.6</version>
</dependency>
配置文件
jdbc.properties
db.url=jdbc:mysql://1.117.173.XXX:3306/db?useUnicode=true&characterEncoding=utf-8&erverTimezone=UTC&useSSL=false
db.user=root
db.password=123456
db.driver=com.mysql.jdbc.Driver
log4j.properties
log4j.rootLogger=DEBUG,CONSOLE,file
#log4j.rootLogger=ERROR,ROLLING_FILE
log4j.logger.cn.siliang.dao=debug
log4j.logger.com.ibatis=debug
log4j.logger.com.ibatis.common.jdbc.SimpleDataSource=debug
log4j.logger.com.ibatis.common.jdbc.ScriptRunner=debug
log4j.logger.com.ibatis.sqlmap.engine.impl.SqlMapClientDelegate=debug
log4j.logger.java.sql.Connection=debug
log4j.logger.java.sql.Statement=debug
log4j.logger.java.sql.PreparedStatement=debug
log4j.logger.java.sql.ResultSet=debug
log4j.logger.org.tuckey.web.filters.urlrewrite.UrlRewriteFilter=debug
# Console Appender
log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
log4j.appender.Threshold=error
log4j.appender.CONSOLE.Target=System.out
log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
log4j.appender.CONSOLE.layout.ConversionPattern= [%p] %d %c - %m%n
# DailyRolling Fil
log4j.appender.file=org.apache.log4j.DailyRollingFileAppender
log4j.appender.file.DatePattern=yyyy-MM-dd
log4j.appender.file.File=log.log
log4j.appender.file.Append=true
log4j.appender.file.Threshold=error
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=%d{yyyy-M-d HH:mm:ss}%x[%5p](%F:%L)%m%n
mybatis-config.xml
<configuration>
<!-- 配置文件信息 -->
<properties resource="jdbc.properties"></properties>
<settings>
<setting name="logImpl" value="LOG4J"/>
<setting name="autoMappingBehavior" value="FULL"/>
</settings>
<typeAliases>
<package name="com.test.pojo"/>
</typeAliases>
<plugins>
<plugin interceptor="com.github.pagehelper.PageInterceptor">
<property name="helperDialect" value="mysql"/>
</plugin>
</plugins>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<!-- 从配置文件中加载属性 -->
<property name="driver" value="${db.driver}"/>
<property name="url" value="${db.url}"/>
<property name="username" value="${db.user}"/>
<property name="password" value="${db.password}"/>
</dataSource>
</environment>
</environments>
<mappers>
<!-- 加载前面编写的SQL语句的文件 -->
<mapper resource="./mapper/StudentMapper.xml"/>
</mappers>
</configuration>
Mapper(interface)
public interface StudentMapper {
List<Student> findAll();
int insert(Student student);
int delete(Integer id);
List<Student> findByName(String name);
List<Student> batchFind(int [] arr);
int updata(Student student);
List<Student> findByInfo(Student student);
}
Mapper(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.test.dao.StudentMapper">
<sql id="studentColumn">
id
,name,score,age,gender
</sql>
<insert id="insert">
insert into student(name, score, age, gender)
values (#{name}, #{score}, #{age}, #{gender});
<!--根据LAST_INSERT_ID()函数获得最后一个添加的id,order="AFTER":mysql是添加之后取id-->
<selectKey keyProperty="id" order="AFTER" resultType="int">
select LAST_INSERT_ID();
</selectKey>
</insert>
<!--useGeneratedKeys="true":JDBC对于支持自动生成记录主键的数据库,如:MySQL,SQL Server,此时设置useGeneratedKeys参数值为true,
在执行添加记录之后可以获取到数据库自动生成的主键ID。
keyProperty: 映射javabean中的主键-->
<!--<insert id="insert" parameterType="com.test.pojo.Student" useGeneratedKeys="true" keyProperty="id">
insert into student(name, score, age, gender)
values (#{name}, #{score}, #{age}, #{gender});
</insert>-->
<!--prefix:添加前缀
suffix:添加后缀
prefixOverrides:删除前缀
suffixOverrides:删除后缀-->
<!--<update id="updata">
update student
<trim prefix="set" suffix="where id = #{id}" suffixOverrides=",">
<if test="name != null and name != ''">
name = #{name},
</if>
<if test="age != null and age gt 0 and age lte 100">
age = #{age},
</if>
</trim>
</update>-->
<update id="updata">
update student
<!--常用不解释-->
<set>
<if test="name != null and name != ''">
name = #{name},
</if>
<if test="age != null and age gt 0 and age lte 100">
age = #{age},
</if>
</set>
where id = #{id}
</update>
<delete id="delete">
delete
from student
where id = #{id};
</delete>
<select id="findAll" resultType="com.test.pojo.Student">
select
<include refid="studentColumn"></include>
from student;
</select>
<!--这里要注意,#{}无法在模糊查询中使用.-->
<select id="findByName" resultType="com.test.pojo.Student">
select *
from student
where name like '%${value}%';
</select>
<select id="findByInfo" parameterType="com.test.pojo.Student" resultType="com.test.pojo.Student">
select
<include refid="studentColumn"></include>
from student
<where>
<!--等同于java中switch-->
<choose>
<when test="id != null">
and id = #{id}
</when>
<when test="name != null">
and name = #{name}
</when>
<otherwise>
and age = 18
</otherwise>
</choose>
</where>
</select>
<select id="batchFind" resultType="com.test.pojo.Student" parameterType="java.lang.Integer">
select * from student
<where>
<if test="array != null and array.length > 0">
and id in
<!--collection:遍历的集合,item:每次循环中的元素,open:遍历开始时添加,
close:遍历结束时添加,separator:每个元素中间添加-->
<foreach collection="array" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</if>
</where>
</select>
</mapper>
Test
public class Test {
private SqlSessionFactory sqlSessionFactory;
@Before
public void init() throws IOException {
InputStream resourceAsStream = Resources.getResourceAsStream("mybatis-config.xml");
sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
}
@Test
public void test() {
SqlSession sqlSession = sqlSessionFactory.openSession();
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
List<Student> studentList = mapper.findAll();
studentList.forEach(System.out::println);
}
@Test
public void test1() {
SqlSession sqlSession = sqlSessionFactory.openSession();
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
Student student = new Student();
student.setName("zs");
student.setAge(18);
student.setScore(1);
student.setGender(1);
System.out.println(mapper.insert(student));
System.out.println(student.getId());
sqlSession.commit();
}
@Test
public void test2() {
SqlSession sqlSession = sqlSessionFactory.openSession();
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
System.out.println(mapper.delete(7));
sqlSession.commit();
}
@Test
public void test3() {
SqlSession sqlSession = sqlSessionFactory.openSession();
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
List<Student> list = mapper.findByName("李状");
list.forEach(System.out::println);
}
@Test
public void test4() {
SqlSession sqlSession = sqlSessionFactory.openSession();
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
List<Student> list = mapper.batchFind(new int[]{
1,2,3,4,5,6});
list.forEach(System.out::println);
}
@Test
public void test5() {
SqlSession sqlSession = sqlSessionFactory.openSession();
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
System.out.println(mapper.updata(new Student(9, "zsss", 0, 0, 0)));
sqlSession.commit();
}
@Test
public void test6() {
SqlSession sqlSession = sqlSessionFactory.openSession();
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
List<Student> list = mapper.findByInfo(new Student(null, null, 0, 0, 0));
list.forEach(System.out::println);
}
}
配置文件简述
全局配置文件中,各个标签要按照如下顺序进行配置,因为mybatis加载配置文件的源码中是按照这个顺序进行解析的。
<configuration>
<!-- 配置顺序如下
properties:
一般将数据源的信息单独放在一个properties文件中,然后用这个标签引入,在下面 environment标签中,就可以用${}占位符快速获取数据源的信息
settings:
用来开启或关闭mybatis的一些特性,比如可以用<setting name="lazyLoadingEnabled" value="true"/>来开启延迟加载,可以用<settings name="cacheEnabled" value="true"/>来开启二级缓存
typeAliases:
起别名。在mapper.xml中需要使用parameterType和resultType属性来配置SQL语句的输入参数 类型和输出参数类型,类必须要写上全限定名,比如一个SQL的返回值映射为Student类,则 resultType属性要写com.xxx.Student,这太长了,所以可以用别名来简化书写。 <package name="com.test.pojo"/>,如此,指定包下的所有类,都会以简单类名的小写形式,作为它的别 名。如此,指定包下的所有类,都会以简单类名的小写形式,作为它的别名。
另外,对于基本的Java类型 -> 8大基本类型以及包装类,以及String类型,mybatis提供了默认的 别名,别名为其简单类名的小写,比如原本需要写java.lang.String,其实可以简写为string
typeHandlers:
用于处理Java类型和Jdbc类型之间的转换,mybatis有许多内置的TypeHandler,比如 StringTypeHandler,会处理Java类型String和Jdbc类型CHAR和VARCHAR。这个标签用的不多
objectFactory:
mybatis会根据resultType或resultMap的属性来将查询得到的结果封装成对应的Java类,它有一 个默认的DefaultObjectFactory,用于创建对象实例,这个标签用的也不多
plugins:
可以用来配置mybatis的插件,比如在开发中经常需要对查询结果进行分页,就需要用到pageHelper 分页插件,这些插件就是通过这个标签进行配置的。在mybatis底层,运用了责任链模式+动态代理去 实现插件的功能
environments
environment
transactionManager
dataSource
用来配置数据源
mappers:
用来配置mapper.xml映射文件,这些xml文件里都是SQL语句
-->
</configuration>
#{}和${}的区别
一般会采用#{}
,#{}
在mybatis中,最后会被解析为?,作为占位符,其实就是Jdbc的PreparedStatement中的?
占位符,它有预编译的过程,会对输入参数进行类型解析(如果入参是String类型,设置参数时会自动加上引号),可以防止SQL注入。
如果parameterType属性指定的入参类型是简单类型的话(简单类型指的是8种java原始类型再加一个String),#{}中的变量名可以任意,如果入参类型是pojo,比如是Student类那么#{name}
表示取入参对象Student中的name属性,#{age}
表示取age属性,这个过程是通过反射来做的,这不同于${}
,${}
取对象的属性使用的是OGNL(Object Graph Navigation Language)表达式。
而${}
,一般会用在模糊查询的情景,比如SELECT * FROM student WHERE name like '%${name}%';
它的处理阶段在#{}
之前,它不会做参数类型解析,而仅仅是做了字符串的拼接,若入参的Student对象的name属性为zhangsan,则上面那条SQL最终被解析为SELECT * FROM student WHERE name like '%zhangsan%';
而如果使用的是#{}
,这条SQL最终就会变成SELECT * FROM student WHERE name like '%'zhangsan'%';
所以模糊查询只能用${}
。
虽然普通的入参也可以用${}
,但由于${}
不会做类型解析,就存在SQL注入的风险。比如,虽然普通的入参也可以用${}
,但由于${}
不会做类型解析,就存在SQL注入的风险,因为OR '1' = '1'
恒成立,这样攻击者在不需要知道用户名和密码的情况下,也能够完成登录验证。
另外,对于pojo的入参,${}
中获取对象属性的语法和#{}
几乎一样,但${}
在mybatis底层是通过OGNL表达式语言进行处理的,这跟#{}
的反射处理有所不同。对于简单类型(8种java原始类型再加一个String)的入参,${}
中参数的名字必须是value
,SELECT count(1) FROM
user WHERE name like '%${value}%'
。
缓存
一级缓存
默认开启,同一个SqlSesion级别共享的缓存,在一个SqlSession的生命周期内,执行2次相同的SQL查询,则第二次SQL查询会直接取缓存的数据,而不走数据库,当然,若第一次和第二次相同的SQL查询之间,执行了DML(INSERT/UPDATE/DELETE),则一级缓存会被清空,第二次查询相同SQL仍然会走数据库。
一级缓存在下面情况会被清除:
- 在同一个SqlSession下执行增删改操作时(不必提交),会清除一级缓存
- SqlSession提交或关闭时(关闭时会自动提交),会清除一级缓存
- 对mapper.xml中的某个CRUD标签,设置属性flushCache=true,这样会导致该MappedStatement的一级缓存,二级缓存都失效(一个CRUD标签在mybatis中会被封装成一个MappedStatement)
- 在全局配置文件中设置 ,这样会使一级缓存失效,二级缓存不受影响
二级缓存
默认关闭,可通过全局配置文件中的开启二级缓存总开关,然后在某个具体的mapper.xml中增加,即开启了该mapper.xml的二级缓存。二级缓存是mapper级别的缓存,粒度比一级缓存大,多个SqlSession可以共享同一个mapper的二级缓存。注意开启二级缓存后,SqlSession需要提交,查询的数据才会被刷新到二级缓存当中
关联查询
pojo
Stu
@Data
public class Stu {
private Integer id;
private String name;
//多个学生可以是同一个老师,即多对一
private Teacher teacher;
//老师对学生 -对多
private Integer tid;
}
Teacher
@Data
public class Teacher {
private Integer id;
private String name;
//一个老师多个学生
private List<Stu> stus;
}
Interface
public interface StuMapper {
List<Stu> findAll();
List<Stu> findAll2();
}
public interface TeacherMapper {
public Teacher getTeacher(Integer id);
}
xml
StuMapper.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.test.dao.StuMapper">
<!--
获取所有学生及老师的信息
思路:
1.获取所有学生信息
2.根据获取的学生信息tid来获取其老师的信息
实施:
1.做一个结果映射,其结果集的返回值为Stu
2.通过association来多一对一/对多的结果集映射
-->
<select id="findAll" resultType="stu" resultMap="StuTeacher">
select *
from stu
</select>
<resultMap id="StuTeacher" type="stu">
<!-- property:stu中的属性名, column:多放的表的列名, javaType:属性类型-->
<association property="teacher" column="tid" javaType="teacher" select="getTeacher"/>
</resultMap>
<!--
这里传过来的id,只有在一个属性的时候,西面可以写任意值
如果多个属性的情况下,在association中的column多参数配置:
column="key=value,key=value",
为键值对形式,可以是传给下个sql的取值名,value是上面片段中sql查询的字段名
-->
<select id="getTeacher" resultType="teacher">
select *
from teacher
where id = #{id}
</select>
<!--以上方法不常用-->
<!--另外一种实现-->
<select id="findAll2" resultType="stu" resultMap="StuTeacher2">
select
s.id sid, s.name sname, t.id tid, t.name tname
from stu s
join teacher t
on s.tid = t.id
</select>
<resultMap id="StuTeacher2" type="stu">
<id property="id" column="sid"/>
<result property="name" column="sname"/>
<association property="teacher" javaType="teacher">
<id property="id" column="tid"/>
<result property="name" column="tname"/>
</association>
</resultMap>
</mapper>
Teacher.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.test.dao.TeacherMapper">
<!--
集合使用collection
JavaType和ofType都是用来指定对象类型
JavaType是用来指定pojo中属性的类型
ofType指定的是映射到list集合属性中pojo的类型
-->
<select id="getTeacher" resultMap="TeacherStu">
select
t.id tid, t.name tname, s.id sid, s.name sname
from stu s
join teacher t on s.tid = t.id
where t.id = #{id}
</select>
<resultMap id="TeacherStu" type="teacher">
<id property="id" column="tid"/>
<result property="name" column="tname"/>
<collection property="stus" ofType="stu">
<id property="id" column="sid"/>
<result property="name" column="sname"/>
</collection>
</resultMap>
</mapper>
Test(分页)
public class Test{
private SqlSessionFactory sqlSessionFactory;
@Before
public void init() throws IOException {
InputStream resourceAsStream = Resources.getResourceAsStream("mybatis-config.xml");
sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
}
/**
* 一对多
*/
@Test
public void test() {
SqlSession sqlSession = sqlSessionFactory.openSession();
StuMapper mapper = sqlSession.getMapper(StuMapper.class);
List<Stu> list = mapper.findAll();
list.forEach(System.out::println);
}
/**
* 一对多
*/
@Test
public void test1() {
SqlSession sqlSession = sqlSessionFactory.openSession();
StuMapper mapper = sqlSession.getMapper(StuMapper.class);
List<Stu> list = mapper.findAll2();
list.forEach(System.out::println);
}
/**
* 多对一
*/
@Test
public void test2() {
SqlSession sqlSession = sqlSessionFactory.openSession();
TeacherMapper mapper = sqlSession.getMapper(TeacherMapper.class);
Teacher teacher = mapper.getTeacher(1);
System.out.println(teacher);
}
/**
* 分页
*/
@Test
public void test3() {
SqlSession sqlSession = sqlSessionFactory.openSession();
//开启分页
PageHelper.startPage(1, 2);
StuMapper mapper = sqlSession.getMapper(StuMapper.class);
List<Stu> list = mapper.findAll2();
PageInfo<Stu> pageInfo = new PageInfo<>(list);
System.out.println("获取总数:" + pageInfo.getTotal());
list.forEach(System.out::println);
}
}
Mapper mapper = sqlSession.getMapper(TeacherMapper.class);
Teacher teacher = mapper.getTeacher(1);
System.out.println(teacher);
}
/**
* 分页
*/
@Test
public void test3() {
SqlSession sqlSession = sqlSessionFactory.openSession();
//开启分页
PageHelper.startPage(1, 2);
StuMapper mapper = sqlSession.getMapper(StuMapper.class);
List<Stu> list = mapper.findAll2();
PageInfo<Stu> pageInfo = new PageInfo<>(list);
System.out.println("获取总数:" + pageInfo.getTotal());
list.forEach(System.out::println);
}
}