一、简介
MyBatis 是一款优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以使用简单的 XML 或注解来配置和映射原生信息,将接口和 Java 的 POJO(Plain Ordinary Java Object,即普通的 Java对象)映射成数据库中的记录。
简单来说,MyBatis的优点就是将SQL和Java编码分开,功能边界清晰,一个专注业务、一个专注数据。
二、环境搭建
1、引入MyBatis相关Jar包
2、创建数据库和数据表:MyBatis不能自动建表,表需手动创建,也可结合JPA,由JPA负责自动建表
3、创建和表对应的实体类:该类无需任何注解,就是一个纯粹的Java类
4、创建daos包,并在其下创建XxxMapper接口,并在接口中声明要实现的方法:接口的名字其实是任意的,一般以Mapper作为后缀,该接口无需继承任何其他接口,接口的实现会由MyBatis根据配置来完成
public interface StudentMapper {
public void add(Student stu);
public void update(Student stu);
public void delete(Integer id);
public Student getById(Integer id);
public List<Student> getAll();
}
5、在XxxMapper接口的同一个包下创建XxxMapper.xml文件:配置内容参照官方文档,该文件的名字是任意的,并非一定要和XxxMapper接口同名,习惯上如此,该配置文件的位置也不强制和接口在同一个包下
<?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指定该映射对应的接口,必须使用全类名,MyBatis会去实现该接口 -->
<mapper namespace="com.bdm.mappers.StudentMapper">
<!-- id对应接口中声明的方法名,parameterType是方法需要的参数类型(此处使用的全类名,可以在有其他配置的情况下使用简化类名,mybatis支持基本数据类型的简化——有表可查),
另外如果方法有返回值的话,还需要配置resultType属性值为返回值类型,中间部分是该方法要执行的sql语句,记住是存粹的sql语句 -->
<insert id="add" parameterType="com.bdm.entities.Student">
INSERT INTO students(id,name,age,email)
VALUES(#{id},#{name},#{age},#{email})
</insert>
<update id="update" parameterType="com.bdm.entities.Student">
UPDATE students
SET name=#{name},age=#{age},email=#{email}
WHERE id=#{id}
</update>
<delete id="delete" parameterType="integer">
DELETE FROM students WHERE id = #{id}
</delete>
<select id="getById" parameterType="integer" resultType="com.bdm.entities.Student">
SELECT * FROM students WHERE id = #{id}
</select>
<select id="getAll" resultType="com.bdm.entities.Student">
SELECT * FROM students
</select>
</mapper>
6、配置MyBatis的核心配置文件:mybatis-config.xml,配置内容参考官方文档
<?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">
<!-- 事务控制由于尚未引入Spring等,暂且使用基础的JDBC进行事物控制 -->
<transactionManager type="JDBC" />
<!-- 配置数据源,也可引用c3p0外部文件 -->
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver" />
<!--若使用高版本的Mysql驱动,则需要配置serverTimezone参数,否则会报时区错误-->
<property name="url" value="jdbc:mysql:///smve_test?serverTimezone=UTC" />
<property name="username" value="root" />
<property name="password" value="123456" />
</dataSource>
</environment>
</environments>
<mappers>
<!-- 映射的文件,也可以是类:框架根据此处的配置创建相应的实现类 -->
<mapper resource="com/bdm/mappers/StudentMapper.xml" />
</mappers>
</configuration>
7、添加log4j配置文件:在src根目录下创建log4j.xml文件,用于打印日志信息
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
<!-- 日志的样式 -->
<appender name="log.console" class="org.apache.log4j.ConsoleAppender">
<layout class="org.apache.log4j.PatternLayout">
<!-- 日志的样式 -->
<param name="ConversionPattern" value="%d{yyyy-MM-dd HH:mm:ss,SSS} %5p (%C{1}:%M) - %m%n" />
</layout>
</appender>
<logger name="org.apache.ibatis">
<level value="info" />
<appender-ref ref="log.console" />
</logger>
<!-- 扫描的包名:配置打印哪些包下的日志 -->
<logger name="com.bdm">
<level value="debug" />
<appender-ref ref="log.console" />
</logger>
</log4j:configuration>
8、测试
public class MainTest {
// mybatis核心配置文件的地址:本例是将其放在了src根目录下
private static final String SOURCE = "mybatis-config.xml";
// mybatis中使用sqlSessionFactory获取数据库连接,该对象可以是全局的
private static SqlSessionFactory sqlSessionFactory = null;
// 相当于数据库连接,该对象必须每个方法一个,不能共用,此处采用切面的方式创建,故将其声明在了外面
private SqlSession sqlSession = null;
static {
InputStream inputStream;
try {
// 1、加载配置文件到内存:由此可知该配置文件的名字可以是任意的
inputStream = Resources.getResourceAsStream(SOURCE);
// 2、利用该配置文件的配置构建sqlSessionFactory对象
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
} catch (IOException e) {
e.printStackTrace();
}
}
@Before
public void before() {
// 3、获取sqlSession对象
sqlSession = sqlSessionFactory.openSession();
}
@Test
public void test() {
// 4、调用sqlSession对象的getMapper()方法,获取接口实现类的代理对象,用于调用接口中声明的方法
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
Student stu = new Student();
stu.setId("1");
stu.setAge(23);
stu.setName("tommy");
stu.setEmail("tommy@qq.com");
// 5、调用接口中声明的方法
mapper.add(stu);
}
@After
public void after() {
try {
// 6、提交事务,除了查询之外的其他操作都需要提交事务,否则操作不生效,在数据库中看不到效果
sqlSession.commit();
} catch (Exception e) {
e.printStackTrace();
} finally {
// 7、关闭sessioin
sqlSession.close();
}
}
}
注意
:在实际开发时sqlSession对象不要声明成实例变量,更不要声明成全局变量,而必须是方法内的一个局部变量,因为sqlSession的作用是用来获取数据库连接,类似于Connection,若声明成实例变量或全局变量若其他方法关闭了sqlSession,则再使用时就会报错,即使其他方法不关闭也不好进行事务控制(比如说方法A和方法B使用的是同一个sqlSession,在方法A执行到一半的时候,方法B执行完并调用commit方法提交事务则会将方法A执行到一半的逻辑也提交了,这是错误的)
流程分析:
①sqlSessionFactoryBuilder对象通过build(inputStream)方法读入mybatis-config.xml配置文件,为该文件中配置的mappers接口创建实现类,实现类中会实现哪些方法以及方法怎么实现则由XxxMapper.xml中配置的方法名和功能决定,这也是为什么XxxMapper.xml中的id要和接口的方法名保持一致的原因,否则就无法通过代理类调用方法的实现了(比如接口中声明的方法名为a,XxxMapper.xml中的id为b,则MyBatis为我们创建的实现的对应方法名也会为b而不是a,这样我们在调用我们定义的接口的a方法的时候由于没有对应的实现就不起作用而报错,这一点和SpringData类似)
②在使用的时候,通过SqlSession的getMapper(XxxMapper.class)方法获取到接口实现类的代理对象,通过代理对象即可调用接口中声明了的方法,如①中所述这些方法是由MyBatis框架提供的实现
三、注解版MyBatis
注解版的MyBatis在接口定义的时候有些区别,将方法要执行的SQL在方法声明的时候就指定,这样就摆脱了XxxMapper.xml这个配置文件。但是这样并不好,因为开发中很可能会有一些复杂的SQL,如果写在接口中会很麻烦,而且这也背离了SQL和Java代码分离的初衷,因此最好还是采用XxxMapper.xml配置的方式。如果使用注解版的话,在核心控制文件中的接口就不再是注册XxxMapper.xml文件了,而是注解接口的全类名。
1、接口:接口中的方法在声明时指定执行的SQL
public interface StudentMapper {
@Insert("INSERT INTO students(id,name,age,email) VALUES(#{id},#{name},#{age},#{email})")
public void add(Student stu);
@Update("UPDATE students SET name = #{name},age = #{age},email = #{email} WHERE id = #{id}")
public void update(Student stu);
@Delete("DELETE FROM students WHERE id = #{id}")
public void delete(Integer id);
@Select("SELECT * FROM students WHERE id = #{id}")
public Student getById(Integer id);
@Select("SELECT * FROM students")
public List<Student> getAll();
}
2、核心配置文件:映射的不再是XxxMapper.xml,而是接口的全类名
<mappers>
<!-- 映射的文件,也可以是类:框架根据此处的配置创建相应的实现类 -->
<!-- <mapper resource="com/bdm/mappers/StudentMapper.xml" /> -->
<mapper class="com.bdm.mappers.StudentMapper" />
</mappers>
注意
:需要特别注意的是,在核心配置文件mybatis-config.xml的\<mappers>节点中无论是采用注册接口全类名的方式还是采用注册XxxMapper.xml文件的方式MyBatis在实现接口的时候都会同时实现这两个地方定义的接口方法,举例来说:假如我在\<mappers>中只注册了<mapper class="com.bdm.mappers.StudentMapper" />,这意味着我想使用注解版的MyBatis,那我必然会在声明方法的时候给方法加上相应的@Insert等注解和SQL,你可能会觉得此时StudentMapper.xml配置文件就不会起作用了,因为mybatis-config.xml中并没有注册StudentMapper.xml,因此StudentMapper.xml是不会被加载的。其实不然,MyBatis会同时扫描同名包下有无和StudentMapper.java接口同名的StudentMapper.xml(不同名则不会加载,更不会实现)文件,如果扫描到了,则在实现方法的时候会同时将StudentMapper.xml中定义的方法也进行实现,那么如果StudentMapper.xml文件中有id和接口方法名同名且入参也和方法名一样的配置时,则会报错!原因很简单,一个类中不能有两个同名且入参也相同的方法。反过来如果你在\<mappers>中只注册<mapper resource="com/bdm/mappers/StudentMapper.xml" />,则MyBatis在实现的时候会扫描同名包下是否有StudentMapper.java接口,然后会去实现StudentMapper.java中标注了@Insert等注解的方法。当然如果接口中声明的方法既没有加注解也没有在XxxMapper.xml中配置,则MyBatis框架不会为我们实现该方法。
说了这么多,结论是接口中声明的方法要么只在接口中使用注解,要么只在对应的mapper.xml文件中指明实现方式,这两种方式不能同时存在。但这并不意味着注解版和配置版不能同时存在,我们可以在注解版中实现其中的一部分,另外一部分放在配置中也是可以的。但只能在<mappers>中注册一个,其实注册一个就相当于两种方式都注册了。