MyBatis 是支持普通 SQL查询,存储过程和高级映射的优秀持久层框架。MyBatis 消除了几乎所有的JDBC代码和参数的手工设置以及结果集的检索。MyBatis 使用简单的 XML或注解用于配置和原始映射,将接口和 Java 的POJOs(Plain Ordinary Java Objects,普通的 Java对象)映射成数据库中的记录。
Mybatis和JDBC的对比
通过上面的介绍,我们知道 MyBatis 是来和数据库打交道。那么在这之前,我们是使用 JDBC 来对数据库进行增删改查等一系列操作的,而我们之所以会放弃使用 JDBC,转而使用 MyBatis 框架,这是为什么呢?或者说使用 MyBatis 对比 JDBC 有什么好处?
数据类型转换 => 交给框架
数据校验 => 交给框架
将增删改查集中管理 => 交给框架
异常的统一处理
简化jdbc => 交给框架
使用框架组合各种技术
上面的这些问题我们都可以规避掉,将它们交给框架去管理;
而这个框架就是Mybatis,那我们就来学习一下Mybatis;
Mybatis使用
使用步骤:
- 添加依赖
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.6</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
- 添加配置文件 mybatis-config.xml
用这个配置文件来告诉 mybatis 如何连接数据库
告诉 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"/>
<!-- 配置了 mybatis 连接数据库的信息 -->
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/test?useSSL=false"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
<!--向 mybatis-config.xml 配置文件中注册 StudentMapper.xml 文件 -->
<mappers>
<!-- 指定映射文件的位置 包名+文件名(文件路径)-->
<mapper resource="mapper/StudentMapper.xml"/>
</mappers>
</configuration>
-
提供一个 映射文件
用来管理 sql 语句,描述 sql 语句与数据库表之间的映射关系 -
调用 mybatis api 使用映射文件真正执行增删改查
Configuration 对应 mybatis-config.xml
SqlSessionFactory 用来创建 SqlSession 的
SqlSession (接口)
.insert
.update
.delete
.selectOne
.selectList
Mybatis 映射文件(重点)
<?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">
<!-- namespace(命名空间) 用来防止 sql 的命名冲突的 -->
<mapper namespace="mapper.StudentMapper">
新增
<!-- #{sname} 用来获取 Student 参数对象中的 sname属性-->
<!-- useGeneratedKeys="true" 是告诉 mybatis 要使用由数据库产生的主键值 -->
<!-- keyProperty="主键对应的属性名" -->
<insert id="abc" parameterType="domain.Student"
useGeneratedKeys="true" keyProperty="sid">
insert into student(sid, sname, birthday, sex)
values ( null, #{name}, #{birthday}, #{sex})
</insert>
删除
<delete id="delete" parameterType="int">
delete from student where sid = #{sid}
</delete>
查询所有
<select id="findAll" resultType="domain.Student">
select sid,sname name,birthday,sex from student
</select>
根据id查询
<select id="findById" resultType="domain.Student" parameterType="int">
select sid,sname name,birthday,sex from student where sid = #{sid}
</select>
更新
<update id="update" parameterType="domain.Student">
update student set sname=#{name}, birthday=#{birthday}, sex=#{sex} where sid=#{sid}
</update>
<!-- 动态更新列
Student stu = new Student();
stu.setSid(1001);
stu.setSex("男");
update student set sex=#{sex} where sid=#{sid}
用 set 标签可以去除多余的逗号
-->
<update id="update" parameterType="domain.Student">
update student
<set>
<if test="name != null">
sname=#{name},
</if>
<if test="birthday != null">
birthday=#{birthday},
</if>
<if test="sex != null">
sex=#{sex},
</if>
</set>
where sid=#{sid}
</update>
分页查询
<!-- m , n
java.util.Map -> map
java.util.List -> list
java.lang.String -> string
map.put("m", 0);
map.put("n", 5);
-->
<select id="findByPage" parameterType="map" resultType="domain.Student">
select sid,sname name,birthday,sex from student limit #{m}, #{n}
</select>
多条删除,条件查询
<!--
(1008,1011,1012
delete from student where id in (1008,1011,1012)-->
<delete id="deleteByIds" parameterType="list">
delete from student where sid in
<foreach collection="list" item="x" open="(" close=")" separator=",">#{x}</foreach>
</delete>
<!-- map
sname=?
birthday=?
sex=?
-->
<!--<select id="findByCondition" parameterType="map" resultType="domain.Student">
select sid,sname name,birthday,sex from student
where 1=1
<if test="sname!=null">
and sname = #{sname}
</if>
<if test="birthday!=null">
and birthday = #{birthday}
</if>
<if test="sex != null">
and sex = #{sex}
</if>
</select>-->
<select id="findByCondition" parameterType="map" resultType="domain.Student">
select sid,sname name,birthday,sex from student
<where> <!-- 可以用 where 去除多余的 and -->
<if test="testname!=null">
and sname = #{testname}
</if>
<if test="birthday!=null">
and birthday = #{birthday}
</if>
<if test="sex != null">
and sex = #{sex}
</if>
</where>
</select>
</mapper>
测试方法编写
public class TestStudentMapper {
static SqlSessionFactory factory;
static {
// 读取配置文件
try(InputStream in = Resources.getResourceAsStream("mybatis-config.xml")){
// 创建 sqlSession 工厂类
factory = new SqlSessionFactoryBuilder().build(in);
} catch (IOException e) {
e.printStackTrace();
}
}
@Test
public void testInsert() throws IOException {
// 创建 sqlSession, 这里的 session 更类似于 jdbc 中的 Connection
SqlSession sqlSession = factory.openSession();
// 执行新增
Student stu = new Student();
stu.setName("李五");
stu.setBirthday(new Date());
stu.setSex("男");
// 参数1 : namespace+sql_id , 参数2: 传递sql语句需要的java对象
sqlSession.insert("mapper.StudentMapper.abc", stu );
// 执行 增删改, 没有启用自动提交事务
sqlSession.commit();
System.out.println("sid的值:" + stu.getSid());
// 关闭资源
sqlSession.close();
}
}
这里要注意,我们使用mybatis时要创建一个sqlsessionFactory的工厂类,sqlsession 接口中有各种操作数据的常用方法,方法参数则对应你映射文件中的sql语句
sqlSession.insert(“mapper.StudentMapper.abc”, stu );
参数一对应StudnetMapper.xml中命名好的mapper中的方法
<!-- namespace(命名空间) 用来防止 sql 的命名冲突的 -->
<mapper namespace="mapper.StudentMapper">
<!-- #{sname} 用来获取 Student 参数对象中的 sname属性-->
<!-- useGeneratedKeys="true" 是告诉 mybatis 要使用由数据库产生的主键值 -->
<!-- keyProperty="主键对应的属性名" -->
<insert id="abc" parameterType="domain.Student"
useGeneratedKeys="true" keyProperty="sid">
insert into student(sid, sname, birthday, sex)
values ( null, #{name}, #{birthday}, #{sex})
</insert>
参数二代表你映射文件需要的参数对象,stu 是domain.Student类的学生对象;
基于注解的mybatis(注解+XML文件)
其他的操作和上面的一样,而不一样的一点是,我们可以不用XML文件进行sql语句的映射了;这次我们定义一个接口,当中定义的方法上直接加入注解,参数写入我们对应的sql语句就达到了原来的效果了;
首先:
向 mybatis-configuration.xml 配置文件中注册 UserMapper.java 文件
<mappers>
<!-- 指定映射接口的 包名.类名 -->
<mapper class="mapper.StudentMapper"></mapper>
</mappers>
public interface StudentMapper {
@Insert("insert into student(sid,sname,birthday,sex) values(null,#{sname}, #{birthday}, #{sex})")
@Options(useGeneratedKeys = true, keyProperty = "sid")
void insert(Student student);
@Update("update student set sname=#{sname}, birthday=#{birthday}, sex=#{sex} where sid=#{sid}")
void update(Student student);
@Delete("delete from student where sid=#{sid}")
void delete(int sid);
@Select("select * from student")
List<Student> findAll();
@Select("select * from student where sid = #{sid}")
Student findById(int sid);
@Select("select * from student limit #{m}, #{n}")
List<Student> findByPage(Map map);
// 同时根据姓名和性别查询
//多参数的时候需要使用@Param注解,给传入的参数做对应
@Select("select * from student where sname=#{a} and sex=#{b}")
List<Student> find1(@Param("a") String sname, @Param("b") String sex);
/*
方法不能重载
@Select("select * from student where sname=#{a}")
List<Student> find1(String sname);*/
@Select("select * from student where sname=#{a} and sex=#{b}")
List<Student> find2(Map<String, Object> map);
void deleteByIds(List<Integer> ids);
}
而它的测试:
public class TestStudentMapper {
static SqlSessionFactory factory;
static {
// 读取配置文件
try(InputStream in = Resources.getResourceAsStream("mybatis-config.xml")){
// 创建 sqlSession 工厂类
factory = new SqlSessionFactoryBuilder().build(in);
} catch (IOException e) {
e.printStackTrace();
}
}
@Test
public void testFindAll() {
SqlSession sqlSession = factory.openSession();
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
List<Student> list = mapper.findAll();
for (Student student : list) {
System.out.println(student);
}
sqlSession.close();
}
这次我们需要用sqlsession 对象调用getMapper方法,将接口类文件注入进去,得到一个StudentMapper对象,这样我们就可以调用之前用注解映射了sql语句的方法了,而它们亦可以关联数据库,获取我们想要的数据;
但是这里有一个要注意的问题,注解方式的sql语句比较的简单,而我们在完成复杂的逻辑的时候,这种方法可能不如我们之前用XML文件的方式简便直观,因此我们可以将它们两种方法结合起来,在较为简单的sql于继时使用注解方式,而复杂的sql映射时使用XML文件来映射,这样就更加灵活了;
就像上面的void deleteByIds(List ids);方法我们没有用注解,而是用了XML文件去映射的sql语句;
<?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="mapper.StudentMapper">
<delete id="deleteByIds" parameterType="list">
delete from student where sid in
<foreach collection="list" item="x" open="(" close=")" separator=",">#{x}</foreach>
</delete>
</mapper>
而这里要注意的是接口类名和XML文件名的对应,这样他才能在使用注解找不到sql语句时去对应的xml文件中去找映射了;
总结
mybatis相比于JDBC的好处还是十分明显的;
- 问题一:数据库连接,使用时就创建,使用完毕就关闭,这样会对数据库进行频繁的获取连接和关闭连接,造成数据库资源浪费,影响数据库性能。
mybatis中的解决:使用数据库连接池管理数据库连接
- 问题二:将 sql 语句硬编码到程序中,如果sql语句修改了,那么需要重新编译 Java 代码,不利于系统维护
mybatis中的解决:将 sql 语句配置到 xml 文件中,即使 sql 语句变化了,我们也不需要对 Java 代码进行修改,重新编译
- 问题三:在 PreparedStatement 中设置参数,对占位符设置值都是硬编码在Java代码中,不利于系统维护
mybatis中的解决:将 sql 语句以及占位符和参数都配置到 xml 文件中
- 问题四:从 resultset 中遍历结果集时,对表的字段存在硬编码,不利于系统维护
mybatis中的解决:将查询的结果集自动映射为 Java 对象
- 问题五:重复性代码特别多,频繁的 try-catch
mybatis中的解决:将其整合到一个 try-catch 代码块中
- 问题六:缓存做的很差,如果存在数据量很大的情况下,这种方式性能特别低
mybatis中的解决:集成缓存框架去操作数据库
- 问题七:sql 的移植性不好,如果换个数据库,那么sql 语句可能要重写
mybatis中的解决:在 JDBC 和 数据库之间插入第三方框架,用第三方去生成 sql 语句,屏蔽数据库的差异
mybatis自身使用时的注意点:
-
parameterType:指定输入参数的类型
-
resultType:指定输出结果的类型,在select中如果查询结果是集合,那么也表示集合中每个元素的类型
-
#{}:表示占位符,用来接收输入参数,类型可以是简单类型,pojo,HashMap等等如果接收简单类型,#{}可以写成 value 或者其他名称
-
${}:表示一个拼接符,会引起 sql 注入,不建议使用