MyBatis 学习笔记(一)MyBatis的简介与使用以及与其他ORM框架的比较
什么是MyBatis
MyBatis 前身是Apache基金会的开源项目iBatis,在2010年该项目脱离Apache基金会并正式更名为MyBatis,在2013年11月,MyBatis迁移到了GitHub。
MyBatis 是一个轻量级的,半自动的持久化(ORM)框架, 其通过XML映射配置文件或者注解来配置和映射原生类型,接口和Java的POJO(Plain Old Java Objects,普通老式Java对象)为数据库中的记录。之所以是半自动化的框架,是因为其不能像Hibernate一样实现自动生成SQL,其需要用户手动编写SQL语句。方便用户对SQL语句进行优化,适用于大数据量,高并发场景。
MyBatis 是一块比较容易上手的框架,使用者只需要通过简单的学习即可掌握其常用特性。
为什么要使用MyBatis
使用MyBatis访问数据库
首先在pom文件中引入依赖
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.5</version>
</dependency>
正如前面所说MyBatis 是一个半自动持久化框架。所以,需要我们自己来维护sql 语句,编写sql语句的xml文件叫做映射文件。在此处,我建立了一个StudentMapper.xml 文件来维护sql 语句。
<mapper namespace="com.jay.mapper.StudentMapper">
<resultMap id="BaseColumn" type="com.jay.entity.Student">
<id property="id" column="id"/>
<result property="name" column="name"/>
<result property="age" column="age"/>
</resultMap>
<select id="selectByName" resultMap="BaseColumn">
select id,name,age from student where name LIKE '%'#{name}'%'
</select>
</mapper>
上面的sql语句表示的意思是通过学生名称来模糊匹配学生
public interface StudentMapper {
/**
* @param name
* @return
*/
List<Student> selectByName(@Param("name") String name);
}
维护完映射文件和对应的接口之后,我们还需要一个XML配置文件来对MyBatis进行一些核心设置,包括获取数据库连接实例的数据源(DataSource)和决定事务作用域和控制方式的事务管理器(TransactionManager)以及SQL映射文件的位置信息等。本节所使用的配置如下:
<configuration>
<!--加载配置文件->jdbc.properties 数据库文件 -->
<properties resource="jdbc.properties"/>
<!-- 设置一个默认的连接环境信息 -->
<environments default="development">
<!--连接环境信息,取一个任意唯一的名字 -->
<environment id="development">
<!-- mybatis使用jdbc事务管理方式 -->
<transactionManager type="JDBC"/>
<!-- mybatis使用连接池方式来获取连接 -->
<dataSource type="POOLED">
<!-- 配置与数据库交互的4个必要属性 -->
<property name="driver" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</dataSource>
</environment>
</environments>
<!-- 加载映射文件-->
<mappers>
<mapper resource="xml/StudentMapper.xml"/>
</mappers>
</configuration>
到此,MyBatis所需的环境就配置好了,接下来我们将MyBatis跑起来。测试代码如下
private SqlSessionFactory sqlSessionFactory;
@Before
public void setUp() {
String resource = "chapter1/mybatis-cfg.xml";
try {
InputStream inputStream = Resources.getResourceAsStream(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream, "development");
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
@Test
public void mybatisTest() {
SqlSession sqlSession = sqlSessionFactory.openSession();
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
List<Student> studentList = mapper.selectByName("点点");
if (studentList != null) {
System.out.println("----->"+studentList.get(0).toString());
}
sqlSession.commit();
sqlSession.close();
}
如上测试代码,首先,我们根据配置文件得到文件流,然后通过SqlSessionFactoryBuilder工厂类构造器得到SqlSessionFactory,再通过SqlSessionFactory工厂类得到SqlSession。然后根据SqlSession的getMapper()方法得到需要执行的mapper。得到之后调用相应的方法得到结果。
运行结果如下:
使用JDBC访问数据库
现在我们使用原生的JDBC来操作数据库,主要流程有以下几个:1. 加载数据库驱动,2. 连接数据库,3,通过PreparedStatement执行sql得到ResultSet,4,对ResultSet 进行处理。流程固定。
public class JdbcTest {
public static void main(String[] args) {
String driver = "com.mysql.jdbc.Driver";
String url = "jdbc:mysql://localhost:3306/mybatisdemo";
String userName = "root";
String password = "admin";
Connection conn = null;
PreparedStatement statement = null;
ResultSet resultSet = null;
try {
Class.forName(driver);
conn = DriverManager.getConnection(url, userName, password);
String sql = "select id,name,age from student where name LIKE ?";
statement = conn.prepareStatement(sql);
statement.setString(1, "%点点%");
resultSet = statement.executeQuery();
List<Student> studentList = new ArrayList<Student>();
if (resultSet != null) {
while (resultSet.next()) {
Student student = new Student();
student.setId(resultSet.getInt("id"));
student.setName(resultSet.getString("name"));
student.setAge(resultSet.getInt("age"));
studentList.add(student);
}
}
System.out.println("----->执行的sql={}"+sql);
System.out.println("----->resultSet={}"+studentList.get(0).toString());
} catch (Exception e) {
e.printStackTrace();
}finally {
try {
if (statement != null) {
statement.close();
}
if (conn != null) {
conn.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
代码比较简单,执行结果如下:
上面的代码的步骤比较多,但是核心步骤只有两步,分别是执行SQL和处理查询结果。从开发人员的角度来说,我们只关心这两个步骤。原生的JDBC的缺点主要有如下几点:
- 每次为了执行某个SQL需要写很多额外的代码,比如加载驱动,创建数据库连接,代码冗余。
- 将SQL写在代码中,如果要改动SQL,就需要到代码中进行修改,这样做不合适,因为改动了Java代码就需要重新编译Java文件,在打包发布,同时将SQL和Java代码混在一起,会降低代码的可读性,不利于维护。
- 关于执行结果的处理,需要手动的将数据从ResultSet中取出,并设置到我们需要的对象中,如果属性过多,用这种方式处理会非常繁琐。
- 每次都要手动管理数据库连接,使用好之后又要手动关闭。
MyBatis VS JDBC
首先我们来看看MyBatis访问数据库的过程
- 读取配置文件
- 创建SqlSessionFactoryBuilder对象
- 通过SqlSessionFactoryBuilder创建SqlSessionFactory对象
- 通过SqlSessionFactory创建SqlSession
- 为Dao 接口生成代理类
- 调用接口方法访问数据库
需要注意的是,在MyBatis中SqlSessionFactoryBuilder 和 SqlSessionFactory 以及 SqlSession 等对象的作用域和生命周期是不一样的。
SqlSessionFactoryBuilder
这个类可以被实例化,使用和丢弃,一旦创建了SqlSessionFactory,就不需要它了,所以,SqlSessionFactoryBuilder实例的最佳作用域是方法作用域(也就是局部方法变量)
SqlSessionFactory
SqlSessionFactory一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建另外一个实例,使用SqlSessionFactory的最佳实践食杂应用运行期间不要重复创建多起,多次重建SqlSessionFactory被视为一种代码”坏味道“。因此SqlSessionFactory的最佳作用域是应用作用域,有很多方法可以做到,最简单的就是使用单例模式或者静态单例模式。
SqlSession
每个线程都应该有它自己的SqlSession实例,SqlSession的实例不是线程安全的,因此是不能被共享的,所有它的最佳的作用域是请求或方法作用域,绝对不能讲SqlSession实例的引用放在了一个类的静态域,比如Servlet框架中的HttpSession中。
映射器实例
映射器是一些由你创建的,绑定你映射的语句的接口,映射器接口的实例是从SqlSession中获得的,因此从技术层面讲,任何映射器实例的最大作用域是请求他们的的SqlSession相同的,尽管如此,映射器实例的最佳作用域是方法作用域。也就是说,映射器实例应该在调用它们的方法中被请求,用过之后即可丢弃。并不需要显示地关闭映射器实例。
总的来说,MyBatis在易用性上要比JDBC好太多,不过JDBC与MyBatis的目标不同,JDBC是作为一种基础服务,而MyBatis则是构建在基础服务之上的框架。所以JDBC的流程繁琐,从JDBC的角度来说,这里的每一个步骤对于完成数据访问请求来说都是必须的。
使用Spring JDBC访问数据库
Spring JDBC是在JDBC上面做的一层比较薄的封装,主要是为了解决直接使用JDBC的一些痛点,易用性得到了不少的提升。
引入的依赖
<properties>
<spring.version>4.3.17.RELEASE</spring.version>
</properties>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>${spring.version}</version>
</dependency>
在使用Spring JDBC之前我们需要做一些配置,我们新建一个配置文件,命名为application.xml,在此配置文件中,我们配置了
数据库的连接信息dataSource,注册了JdbcTemplate实例。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<context:property-placeholder location="jdbc.properties"/>
<bean id="dataSource" class="org.apache.ibatis.datasource.pooled.PooledDataSource">
<!-- 配置与数据库交互的4个必要属性 -->
<property name="driver" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"/>
</bean>
</beans>
配置完成之后,我们可以写一个测试类来测试一下。
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("classpath:chapter1/application.xml") public class SpringJdbcTest { @Autowired private JdbcTemplate jdbcTemplate; @Test public void testSpringJdbc() { String sql = "select id,name,age from student where name LIKE ?"; List<Student> studentList = jdbcTemplate.query(sql, new Object[]{"%点点%"}, new BeanPropertyRowMapper<Student>(Student.class)); System.out.println("----->执行的sql={}"+sql); System.out.println("----->查询结果={}"+studentList.get(0).toString()); } }
运行结果
从上面的测试代码我们可以看出,相对于原生JDBC,Spring JDBC 易用性大大提升,注入jdbcTemplate之后,我们就可以通过jdbcTemplate来操作,只关注sql的执行以及结果的处理即可。代码简化了很多。但是SQL语句仍然写在代码中。
使用Hibernate 访问数据库
本节会像之前的章节一样,我会先写代码进行演示,然后在对比Hibernate 和MyBatis的区别。
Hibernate 访问数据库的过程演示
首先,在POM文件中添加所需依赖
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>5.3.2.Final</version>
</dependency>
接着进行环境配置,主要是关于数据库方面的配置。
<hibernate-configuration>
<session-factory>
<property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>
<property name="hibernate.connection.url">jdbc:mysql://localhost:3306/mybatisdemo</property>
<property name="hibernate.connection.username">root</property>
<property name="hibernate.connection.password">admin</property>
<property name="hibernate.dialect">org.hibernate.dialect.MySQL5Dialect</property>
<property name="hibernate.show_sql">true</property>
<mapping resource="chapter1/xml/Student.hbm.xml" />
</session-factory>
</hibernate-configuration>
环境配置完成之后,我们接着编写映射文件,将表字段与实体类的属性关联起来。如下Student.hbm.xml
<hibernate-mapping package="com.jay.entity">
<class table="student" name="Student">
<id name="id" column="id">
<generator class="native"/>
</id>
<property name="name" column="name"/>
<property name="age" column="age"/>
<property name="classId" column="class_id"/>
</class>
</hibernate-mapping>
所有配置完成之后,我们就可以开始编写测试代码进行测试:
public class HibernateTest {
private SessionFactory sessionFactory;
@Before
public void init() {
Configuration configuration = new Configuration();
configuration.configure("chapter1/hibernate.cfg.xml");
sessionFactory = configuration.buildSessionFactory();
}
@After
public void destroy() {
sessionFactory.close();
}
@Test
public void testORM() {
System.out.println("--------------ORM Query-------------");
Session session = null;
try {
session = sessionFactory.openSession();
int id = 1;
Student student = session.get(Student.class, id);
System.out.println("ORM Query Result:");
System.out.println(student.toString());
System.out.println();
} finally {
if (Objects.nonNull(session)) {
session.close();
}
}
}
@Test
public void testHQL() {
System.out.println("--------------HQL Query-----------");
Session session = null;
try {
session = sessionFactory.openSession();
String hql = "FROM Student WHERE name=:name";
Query query = session.createQuery(hql);
query.setParameter("name", "点点");
List<Student> studentList = query.list();
System.out.println("HQL Query Result:");
studentList.forEach(System.out::println);
System.out.println();
} finally {
if (Objects.nonNull(session)) {
session.close();
}
}
}
@Test
public void testJpaCriteria() {
System.out.println("-------------JPA Criteria-------------");
Session session = null;
try {
session = sessionFactory.openSession();
CriteriaBuilder criteriaBuilder = session.getCriteriaBuilder();
CriteriaQuery<Student> criteriaQuery = criteriaBuilder.createQuery(Student.class);
// 定义FROM子句
Root<Student> student = criteriaQuery.from(Student.class);
// 构建查询条件
Predicate equal = criteriaBuilder.equal(student.get("name"), "点点");
// 通过具有语义化的方法构建SQL,等价于SELECT ... FROM student WHERE ...
criteriaQuery.select(student).where(equal);
Query<Student> query = session.createQuery(criteriaQuery);
List<Student> studentList = query.getResultList();
System.out.println("JPA Criteria Query Result:");
studentList.forEach(System.out::println);
} finally {
if (Objects.nonNull(session)) {
session.close();
}
}
}
}
如上代码清单所示,我编写了三个测试用例,第一个直接使用Hibernate生成SQL的功能,如果查询比较简单可以采用此种方式,生成的SQL是
select student0_.id as id1_0_0_, student0_.name as name2_0_0_, student0_.age as age3_0_0_, student0_.class_id as class_id4_0_0_ from student student0_ where student0_.id=?
第二个测试用例,我编写了一条HQL语句,并通过Query来设置参数,同样Hibernate在运行时会将HQL转化成对应的SQL,转化后的SQL如下:
select student0_.id as id1_0_, student0_.name as name2_0_, student0_.age as age3_0_, student0_.class_id as class_id4_0_ from student student0_ where student0_.name=?
第三个测试用例,我们使用JPA Criteria 进行查询,JPA Criteria 具有类型安全,面向对象和语义化的特点,使用JPA Criteria,我们可以用写Java 代码的方式进行数据库操作,无需手写SQL,第三个用例和第二个用例进行的是同样的查询,所以生成的SQL区别不大。
测试代码的运行结果:
MyBatis VS Hibernate
至此,我们已经对MyBatis和Hibernate访问数据库的过程都做了一次演示,下面我们来对比下MyBatis和Hibernate
- MyBatis 需要使用者自行维护SQL,灵活性高,方便对sql进行优化,Hibernate 可以自动生成SQL,使用成本小。
- MyBatis 适合于需求变动频繁,业务量的系统,Hibernate 更加适合于变动比较小的系统,比如OA系统
使用Spring Data JPA 访问数据库
首先引入依赖
<properties>
<spring.version>4.3.17.RELEASE</spring.version>
</properties>
<!--///Spring JPA-->
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-jpa</artifactId>
<version>1.11.8.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.6.12</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.6.12</version>
</dependency>
接着添加配置文件application-jpa.xml,主要是配置数据库连接信息,以及事务相关的信息
<!--启用注解配置和包扫描-->
<context:annotation-config/>
<context:component-scan base-package="com.jay"/>
<!--创建Spring Data JPA实例对象-->
<jpa:repositories base-package="com.jay.chapter1"/>
<context:property-placeholder location="jdbc.properties"/>
<bean id="dataSource"
class="org.apache.ibatis.datasource.pooled.PooledDataSource">
<!-- 配置与数据库交互的4个必要属性 -->
<property name="driver" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<bean id="entityManagerFactory"
class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="packagesToScan" value="com.jay.entity"/>
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
<property name="generateDdl" value="true"/>
<property name="showSql" value="true"/>
</bean>
</property>
</bean>
<!--事务管理器-->
<bean id="transactionManager"
class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory"/>
</bean>
<!--事务管理-->
<tx:advice id="transactionAdvice"
transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="get*" read-only="true"/>
<tx:method name="*"/>
</tx:attributes>
</tx:advice>
<aop:config>
<aop:pointcut id="daoPointCut" expression="execution(* com.jay.chapter1.mapper.*.*(..))"/>
<aop:advisor advice-ref="transactionAdvice" pointcut-ref="daoPointCut"/>
</aop:config>
配置文件添加完成之后,接着我们编写一个接口继承CrudRepository接口,使其具备基本的增删改查功能。
public interface JpaStudentDao extends CrudRepository<JpaStudent,Integer>{
/**
* @param name
* @return
*/
List<JpaStudent> getByNameLike(String name);
}
DAO接口添加完成之后,接着我们添加一个测试类进行测试。
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:chapter1/application-jpa.xml")
public class JPATest {
@Autowired
JpaStudentDao jpaStudentDao;
@Before
public void init() {
JpaStudent jpaStudent = new JpaStudent("张三", 12, "121");
jpaStudentDao.save(jpaStudent);
}
@Test
public void testCrudRepostitory() {
List<JpaStudent> jpaStudents = jpaStudentDao.getByNameLike("张三");
jpaStudents.forEach(System.out::println);
System.out.println();
}
}
如上测试类所示,我先使用了JPA自带的save方法向数据库中插入了一条数据,接着自定义了一个查询方法。JPA中查询方法可以由我们声明的命名查询生成,也可以由方法名解析
方法名以find…By, read…By, query…By, count…By和 get…By做开头。在By之前可以添加Distinct表示查找不重复数据。By之后是真正的查询条件。
可以查询某个属性,也可以使用条件进行比较复杂的查询,例如Between, LessThan, GreaterThan, Like,And,Or等。
字符串属性后面可以跟IgnoreCase表示不区分大小写,也可以后跟AllIgnoreCase表示所有属性都不区分大小写。
可以使用OrderBy对结果进行升序或降序排序。
可以查询属性的属性,直接将几个属性连着写即可,如果可能出现歧义属性,可以使用下划线分隔多个属性。
运行结果如下:
MyBatis VS JPA
通过上面的实例,我们可以了解到JPA的使用,JPA类似于Hibernate都可以自动生成SQL,不同之处是,JPA还可以根据方法名来解析生成sql。MyBatis 还是需要使用者自行维护sql。
总结
本篇文章对MyBatis 是什么,为什么使用,以及与其他ORM框架进行了对比。
参考文献
MyBatis官方文档
MyBatis 源码分析系列文章导读
《MyBatis 技术内幕》- 徐郡明
Spring Data JPA 介绍和使用
源代码
https://github.com/XWxiaowei/MyBatisLearn/tree/master/mybatisDemo
作者:码农飞哥
微信公众号:码农飞哥