什么是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访问数据库
<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的缺点主要有如下几点:
1.每次为了执行某个SQL需要写很多额外的代码,比如加载驱动,创建数据库连接,代码冗余。
2.将SQL写在代码中,如果要改动SQL,就需要到代码中进行修改,这样做不合适,因为改动了Java代码就需要重新编译Java文件,在打包发布,同时将SQL和Java代码混在一起,会降低代码的可读性,不利于维护。
3.关于执行结果的处理,需要手动的将数据从ResultSet中取出,并设置到我们需要的对象中,如果属性过多,用这种方式处理会非常繁琐。
4.每次都要手动管理数据库连接,使用好之后又要手动关闭。
MyBatis VS JDBC
首先我们来看看MyBatis访问数据库的过程
1.读取配置文件
2.创建SqlSessionFactoryBuilder对象
3.通过SqlSessionFactoryBuilder创建SqlSessionFactory对象
4.通过SqlSessionFactory创建SqlSession
为Dao 接口生成代理类
6.调用接口方法访问数据库
需要注意的是,在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框架进行了对比。
参考文献
源代码
https://github.com/XWxiaowei/MyBatisLearn/tree/master/mybatisDemo