目录
一、数据库操作框架的历程
1.1 JDBC
JDBC(Java Data Base Connection,java数据库连接)是一种用于执 行SQL语句的Java API,可以为多种关系数据库提供统一访问,它由一组用Java语言编写的类和接口组成。JDBC提供了一种基准,据此可以构建更高级的工具和接口,使数据库开发人员能够编写数据库应用程序。
- 优点:运行期:快捷、高效
- 缺点:编辑期:代码量大、繁琐异常处理、不支持数据库跨平台
jdbc核心api:
- DriverManager 连接数据库
- Connection 连接数据库的抽象
- Statment 执行SQL
- ResultSet 数据结果集
JDBC这种方式的代码流程一般是:1、加载数据库驱动,2、创建数据库连接对象,3、创建 SQL 执行语句对象PreparedStatement,4、执行 SQL得到ResultSet,5、处理ResultSet 结果集。它的过程比较固定,下面我们再手写一遍 JDBC 代码,回忆一下初学 Java 的场景。
public class JdbcTest {
public void testJdbc() {
// 在URL中配置了MYSQL信息,其中包括数据库连接用户名和密码等配置信息
String url = "jdbc:mysql://localhost:3306/myblog?user=root&password=1234&useUnicode=true&characterEncoding=UTF8&useSSL=false";
Connection conn = null;
try {
Class.forName("com.mysql.cj.jdbc.Driver");
// 通过JDBC驱动,获取数据库连接
conn = DriverManager.getConnection(url);
String author = "coolblog.xyz";
String date = "2018.06.10";
String sql = "SELECT id, title, author, content, create_time FROM article WHERE author = '" + author + "' AND create_time > '" + date + "'";
// 使用conn创建Statement对象,用于执行SQL
Statement stmt = conn.createStatement();
// ResultSet用于接收执行SQL的结果 stmt用于执行SQL
ResultSet rs = stmt.executeQuery(sql);
// 通过rs将查询出来的结果转移到JAVA类中,这一步需要我们手动填充
List<Article> articles = new ArrayList<>(rs.getRow());
while (rs.next()) {
Article article = new Article();
article.setId(rs.getInt("id"));
article.setTitle(rs.getString("title"));
article.setAuthor(rs.getString("author"));
article.setContent(rs.getString("content"));
article.setCreateTime(rs.getDate("create_time"));
articles.add(article);
}
System.out.println("Query SQL ==> " + sql);
System.out.println("Query Result: ");
articles.forEach(System.out::println);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
} finally {
try {
// 使用完要关闭连接
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
代码比较简单,就不多说了。下面来看一下测试结果:
上面代码的步骤比较多,但核心步骤只有两部,分别是执行 SQL 和处理查询结果。从开发人员的角度来说,我们也只关心这两个步骤。如果每次为了执行某个 SQL 都要写很多额外的代码。比如打开驱动,创建数据库连接,就显得很繁琐了。当然我们可以将这些额外的步骤封装起来,这样每次调用封装好的方法即可。这样确实可以解决代码繁琐,冗余的问题。不过,使用 JDBC 并非仅会导致代码繁琐,冗余的问题。在上面的代码中,我们通过字符串对 SQL 进行拼接。这样做会导致两个问题,第一是拼接 SQL 可能会导致 SQL 出错,比如少了个逗号或者多了个单引号等。第二是将 SQL 写在代码中,如果要改动 SQL,就需要到代码中进行更改。这样做是不合适的,因为改动 Java 代码就需要重新编译 Java 文件,然后再打包发布。同时,将 SQL 和 Java 代码混在一起,会降低代码的可读性,不利于维护。关于拼接 SQL,是有相应的处理方法。比如可以使用 PreparedStatement,同时还可解决 SQL 注入的问题。
除了上面所说的问题,直接使用 JDBC 访问数据库还会有什么问题呢?这次我们将目光转移到执行结果的处理逻辑上。从上面的代码中可以看出,我们需要手动从 ResultSet 中取出数据,然后再设置到 Article 对象中。好在我们的 Article 属性不多,所以这样做看起来也没什么。假如 Article 对象有几十个属性,再用上面的方式接收查询结果,会非常的麻烦。而且可能还会因为属性太多,导致忘记设置某些属性。以上的代码还有一个问题,用户需要自行处理受检异常,这也是导致代码繁琐的一个原因。哦,还有一个问题,差点忘了。用户还需要手动管理数据库连接,开始要手动获取数据库连接。使用好后,又要手动关闭数据库连接。不得不说,真麻烦。
没想到直接使用 JDBC 访问数据库会有这么多的问题。如果在生产环境直接使用 JDBC,怕是要被 Leader 打死了。当然,视情况而定。如果项目非常小,且对数据库依赖比较低。直接使用 JDBC 也很方便,不用像 MyBatis 那样搞一堆配置了。
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的角度来说,这里的每一个步骤对于完成数据访问请求来说都是必须的。
1.2 DBUtils
DBUtils是Java编程中的数据库操作实用工具,小巧简单实用。DBUtils封装了对JDBC的操作,简化了JDBC操作,可以少写代码。
DBUtils三个核心功能介绍:
- QueryRunner中提供对sql语句操作的API
- ResultSetHandler接口,用于定义select操作后,怎样封装结果集
- DBUtils类,它就是一个工具类,定义了关闭资源与事务处理的方法
1.3 Hibernate
Hibernate 是由 Gavin King 于 2001 年创建的开放源代码的对象关系框架。它强大且高效的构建具有关系对象持久性和查询服务的 Java 应用程序。
Hibernate 将 Java 类映射到数据库表中,从 Java 数据类型中映射到 SQL 数据类型中,并把开发人员从 95% 的公共数据持续性编程工作中解放出来。
Hibernate 是传统 Java 对象和数据库服务器之间的桥梁,用来处理基于 O/R 映射机制和模式的那些对象。它是一种全自动的ORM框架 。
ORM(对象关系映射):
- O:object java对象
- R:relational 关系型数据
- M:mapping 映射
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区别不大。
测试代码的运行结果:
Hibernate 优势
- Hibernate 使用 XML 文件来处理映射 Java 类别到数据库表格中,并且不用编写任何代码。
- 为在数据库中直接储存和检索 Java 对象提供简单的 APIs。
- 如果在数据库中或任何其它表格中出现变化,那么仅需要改变 XML 文件属性。
- 抽象不熟悉的 SQL 类型,并为我们提供工作中所熟悉的 Java 对象。
- Hibernate 不需要应用程序服务器来操作。
- 操控你数据库中对象复杂的关联。
- 最小化与访问数据库的智能提取策略。
- 提供简单的数据询问。
Hibernate劣势
- hibernate的完全封装导致无法使用数据的一些功能。
- Hibernate的缓存问题。
- Hibernate对于代码的耦合度太高。
- Hibernate寻找bug困难。
- Hibernate批量数据操作需要大量的内存空间而且执行过程中需要的对象太多
MyBatis VS Hibernate
- MyBatis 需要使用者自行维护SQL,灵活性高,方便对sql进行优化,Hibernate 可以自动生成SQL,使用成本小。
- MyBatis 适合于需求变动频繁,业务量的系统,Hibernate 更加适合于变动比较小的系统,比如OA系统
1.4 Spring JDBC:JDBCTemplate
Spring JDBC是在JDBC上面做的一层比较薄的封装,构造了JdbcTemplate,主要是为了解决直接使用JDBC的一些痛点,易用性得到了不少的提升。
JdbcTemplate针对数据查询提供了多个重载的模板方法,你可以根据需要选用不同的模板方法。如果你的查询很简单,仅仅是传入相应SQL或者相关参数,然后取得一个单一的结果,那么你可以选择如下一组便利的模板方法。
- 优点:运行期:高效、内嵌Spring框架中、支持基于AOP的声明式事务
- 缺点:必须于Spring框架结合在一起使用、不支持数据库跨平台、默认没有缓存
JdbcTemplate的使用:
引入的依赖
<properties>
<spring.version>4.3.17.RELEASE</spring.version>
</properties>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</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语句仍然写在代码中。
1.5 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>
接着添加配置文件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?
Mybatis前身是iBatis,其源于“Internet”和“ibatis”的组合,MyBatis 是一款优秀的持久层半自动的ORM框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。但是MyBatis的自动化程度不高,移植性也不高,有时从一个数据库迁移到另外一个数据库的时候需要自己修改配置,所以称只为半自动ORM框架。
2.1 传统JDBC与MyBatis相比的弊病
传统JDBC
@Test
public void test() throws SQLException {
Connection conn=null;
PreparedStatement pstmt=null;
try {
// 1.加载驱动
Class.forName("com.mysql.jdbc.Driver");
// 2.创建连接
conn= DriverManager.
getConnection("jdbc:mysql://localhost:3306/mybatis_example", "root", "123456");
// SQL语句
String sql="select id,user_name,create_time from t_user where id=?";
// 获得sql执行者
pstmt=conn.prepareStatement(sql);
pstmt.setInt(1,1);
// 执行查询
pstmt.execute();
ResultSet rs= pstmt.getResultSet();
rs.next();
User user =new User();
user.setId(rs.getLong("id"));
user.setUserName(rs.getString("user_name"));
user.setCreateTime(rs.getDate("create_time"));
System.out.println(user.toString());
} catch (Exception e) {
e.printStackTrace();
}
finally{
// 关闭资源
try {
if(conn!=null){
conn.close();
}
if(pstmt!=null){
pstmt.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
传统JDBC的问题如下:
- 数据库连接创建,释放频繁造成西戎资源的浪费,从而影响系统性能,使用数据库连接池可以解决问题。
- sql语句在代码中硬编码,造成代码的不已维护,实际应用中sql的变化可能较大,sql代码和java代码没有分离开来维护不方便。
- 使用preparedStatement向有占位符传递参数存在硬编码问题因为sql中的where子句的条件不确定,同样是修改不方便/
- 对结果集中解析存在硬编码问题,sql的变化导致解析代码的变化,系统维护不方便。
MyBatis对传统的JDBC的解决方案:
- 数据库连接创建、释放频繁造成系统资源浪费从而影响系统性能,如果使用数据库连接池可解决此问题。
- 解决:在SqlMapConfig.xml中配置数据连接池,使用连接池管理数据库链接。
- Sql语句写在代码中造成代码不易维护,实际应用sql变化的可能较大,sql变动需要改变java代码。
- 解决:将Sql语句配置在XXXXmapper.xml文件中与java代码分离。
- 向sql语句传参数麻烦,因为sql语句的where条件不一定,可能多也可能少,占位符需要和参数一一对应。
- 解决:Mybatis自动将java对象映射至sql语句,通过statement中的parameterType定义输入参数的类型。
- 对结果集解析麻烦,sql变化导致解析代码变化,且解析前需要遍历,如果能将数据库记录封装成pojo对象解析比较方便。
- 解决:Mybatis自动将sql执行结果映射至java对象,通过statement中的resultType定义输出结果的类型。
一个Mybatis最简单的使用列子如下:
public class App {
public static void main(String[] args) {
String resource = "mybatis-config.xml";
Reader reader;
try {
// 将XML配置文件构建为Configuration配置类
reader = Resources.getResourceAsReader(resource);
// 通过加载配置文件流构建一个SqlSessionFactory DefaultSqlSessionFactory
SqlSessionFactory sqlMapper = new SqlSessionFactoryBuilder().build(reader);
// 通过SqlSessionFactory获取数据源执行器 DefaultSqlSession
SqlSession session = sqlMapper.openSession();
try {
// 执行查询 底层执行jdbc
//User user = (User)session.selectOne("com.tuling.mapper.selectById", 1);
// 通过mapper对象执行sql
UserMapper mapper = session.getMapper(UserMapper.class);
System.out.println(mapper.getClass());
User user = mapper.selectById(1L);
System.out.println(user.getUserName());
} catch (Exception e) {
e.printStackTrace();
}finally {
session.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
总结下就是分为下面四个步骤:
- 从配置文件(通常是XML文件)得到SessionFactory;
- 从SessionFactory得到SqlSession;
- 通过SqlSession进行CRUD和事务的操作;
- 执行完相关操作之后关闭Session。
2.2 MyBatis中的组件
通过上面的讲解,我们就知道了MyBatis中有下面四个重要的组件:
- SqlSessionFactoryBuilder:读取配置信息创建SqlSessionFactory,建造者模式,方法级别生命周期;
- SqlSessionFactory:创建Sqlsession,工厂单例模式,存在于程序的整个生命周期;
- SqlSession:代表一次数据库连接,一般通过调用Mapper访问数据库,也可以直接发送SQL执行;线程不安全,要保证线程独享(方法级);
- SQL Mapper:由一个Java接口和XML文件组成,包含了要执行的SQL语句和结果集映射规则。方法级别生命周期;
2.3 MyBatis的体系结构
MyBatis是一个半自动的ORM框架,除了需要编写POJO和映射关系之外,还需要编写SQL语句。MyBatis映射文件三要素:
- SQL
- 映射规则
- POJO
优点:
- 与JDBC相比,减少了50%的代码量
- 最简单的持久化框架,简单易学
- SQL代码从程序代码中彻底分离出来,可以重用
- 提供XML标签,支持编写动态SQL
- 提供映射标签,支持对象与数据库的ORM字段关系映射
- 支持缓存、连接池、数据库移植....
缺点:
- SQL语句编写工作量大,熟练度要高
- 数据库移植性比较差,如果需要切换数据库的话,SQL语句会有很大的差异
Mybaits整体体系图
三、快速搭建MyBatis项目
1、创建普通的maven项目
2、导入相关的依赖
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>cn.tulingxueyuan</groupId>
<artifactId>mybatis_helloworld</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<!-- mybatis依赖 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.4</version>
</dependency>
<!-- 数据库驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
</dependencies>
</project>
驱动请按照数据库版本进行对应 MySQL :: MySQL Connector/J 8.1 Release Notes
3、创建对应的数据表
4、创建与表对应的实体类对象
emp.java
package cn.tulingxueyuan.pojo;
public class Emp {
private Integer id;
private String username;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
@Override
public String toString() {
return "Emp{" +
"id=" + id +
", username='" + username + '\'' +
'}';
}
}
5、创建对应的Mapper接口
其实这个所谓的Mapper接口,就是我们编写的dao层接口,两个是一样的,叫mapper或者dao都可以,总之这个接口就是要和mapper.xml编写的各种查询sql对应上即可。
EmpMapper.java
package cn.tulingxueyuan.mapper;
import cn.tulingxueyuan.pojo.Emp;
import org.apache.ibatis.annotations.Select;
public interface EmpMapper {
// 根据id查询Emp实体
//@Select("select * from emp where id=#{id}")
Emp selectEmp(Integer id);
// 插入
Integer insertEmp(Emp emp);
// 更新
Integer updateEmp(Emp emp);
// 删除
Integer deleteEmp(Integer id);
}
6、编写配置文件
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">
<transactionManager type="JDBC"/>
<!-- 配置数据库连接 -->
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
<mappers>
<!--<mapper resource="EmpMapper.xml"/>-->
<!-- 指定mapper类 -->
<mapper class="cn.tulingxueyuan.mapper.EmpMapper"></mapper>
</mappers>
</configuration>
EmpMapper.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类 -->
<mapper namespace="cn.tulingxueyuan.mapper.EmpMapper">
<!--根据id查询Emp实体-->
<select id="selectEmp" resultType="cn.tulingxueyuan.pojo.Emp">
select * from Emp where id = #{id}
</select>
<insert id="insertEmp">
INSERT INTO
`mybatis`.`emp` ( `username`)
VALUES (#{username});
</insert>
<update id="updateEmp">
UPDATE EMP
SET username=#{username}
WHERE id=#{id}
</update>
<delete id="deleteEmp">
DELETE FROM emp
WHERE id=#{id}
</delete>
</mapper>
7、编写测试类
MyTest.java
/***
* MyBatis 搭建步骤:
* 1.添加pom依赖 (mybatis的核心jar包和数据库版本对应版本的驱动jar包)
* 2.新建数据库和表
* 3.添加mybatis全局配置文件 (可以从官网中复制)
* 4.修改mybatis全局配置文件中的 数据源配置信息
* 5.添加数据库表对应的POJO对象(相当于我们以前的实体类)
* 6.添加对应的PojoMapper.xml (里面就维护所有的sql)
* 修改namespace: 如果是StatementId没有特殊的要求
* 如果是接口绑定的方式必须等于接口的完整限定名
* 修改对应的id(唯一)、resultType 对应返回的类型如果是POJO需要制定完整限定名
* 7.修改mybatis全局配置文件:修改Mapper
*/
public class MybatisTest {
SqlSessionFactory sqlSessionFactory;
@Before
public void before(){
// 从 XML 中构建 SqlSessionFactory
String resource = "mybatis.xml";
InputStream inputStream = null;
try {
// 通过Resources得到XML配置文件输入流
inputStream = Resources.getResourceAsStream(resource);
} catch (IOException e) {
e.printStackTrace();
}
// 通过SqlSessionFactoryBuilder,将配置文件输入流转换城sqlSessionFactory
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
}
/**
* 基于StatementId的方式去执行SQL
* <mapper resource="EmpMapper.xml"/>
* @throws IOException
*/
@Test
public void test01() {
// 通过sqlSessionFactory获取sqlSession去执行sql
try (SqlSession session = sqlSessionFactory.openSession()) {
Emp emp = (Emp) session.selectOne("cn.tulingxueyuan.pojo.EmpMapper.selectEmp", 1);
System.out.println(emp);
}
}
/**
* 基于接口绑定的方式
* 1.新建数据访问层的接口: POJOMapper
* 2.添加mapper中对应的操作的方法
* 1.方法名要和mapper中对应的操作的节点的id要一致
* 2.返回类型要和mapper中对应的操作的节点的resultType要一致
* 3.mapper中对应的操作的节点的参数必须要在方法的参数中声明
* 3.Mapper.xml 中的namespace必须要和接口的完整限定名要一致
* 4.修改mybatis全局配置文件中的mappers,采用接口绑定的方式:
* <mapper class="cn.tulingxueyuan.mapper.EmpMapper"></mapper>
* 5.一定要将mapper.xml和接口放在同一级目录中,只需要在resources新建和接口同样结构的文件夹就行了,生成就会合并在一起
*
* @throws IOException
*/
@Test
public void test02(){
try (SqlSession session = sqlSessionFactory.openSession()) {
// 通过session创建Mapper接口实现类,用来执行SQL
EmpMapper mapper = session.getMapper(EmpMapper.class);
Emp emp = mapper.selectEmp(1);
System.out.println(emp);
}
}
/**
* 基于注解的方式
* 1.在接口方法上面写上对应的注解,如下@Select
*@Select("select * from emp where id=#{id}")
* 注意:
* 注解可以和xml共用, 但是不能同时存在方法对应的xml的id
*
*/
@Test
public void test03(){
try (SqlSession session = sqlSessionFactory.openSession()) {
EmpMapper mapper = session.getMapper(EmpMapper.class);
Emp emp = mapper.selectEmp(1);
System.out.println(emp);
}
}
}
四、增删改查的基本操作
4.1 通过xml实现mapper
EmpDao.java
其实就是mapper接口,叫法不同而已
package cn.tulingxueyuan.dao;
import cn.tulingxueyuan.bean.Emp;
public interface EmpDao {
public Emp findEmpByEmpno(Integer empno);
public int updateEmp(Emp emp);
public int deleteEmp(Integer empno);
public int insertEmp(Emp emp);
}
EmpDao.xml
其实就是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">
<!--namespace:编写接口的全类名,就是告诉要实现该配置文件是哪个接口的具体实现-->
<mapper namespace="cn.tulingxueyuan.dao.EmpDao">
<!--
select:表示这个操作是一个查询操作
id表示的是要匹配的方法的名称
resultType:表示返回值的类型,查询操作必须要包含返回值的类型
#{属性名}:表示要传递的参数的名称
-->
<select id="findEmpByEmpno" resultType="cn.tulingxueyuan.bean.Emp">
select * from emp where empno = #{empno}
</select>
<!--增删改查操作不需要返回值,增删改返回的是影响的行数,mybatis会自动做判断-->
<insert id="insertEmp">
insert into emp(empno,ename) values(#{empno},#{ename})
</insert>
<update id="updateEmp">
update emp set ename=#{ename} where empno = #{empno}
</update>
<delete id="deleteEmp">
delete from emp where empno = #{empno}
</delete>
</mapper>
MyTest.java
package cn.tulingxueyuan.test;
import cn.tulingxueyuan.bean.Emp;
import cn.tulingxueyuan.dao.EmpDao;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.Before;
import org.junit.Test;
import java.io.IOException;
import java.io.InputStream;
public class MyTest {
SqlSessionFactory sqlSessionFactory = null;
@Before
public void init() {
// 根据全局配置文件创建出SqlSessionFactory
// SqlSessionFactory:负责创建SqlSession对象的工厂
// SqlSession:表示跟数据库建议的一次会话
String resource = "mybatis-config.xml";
InputStream inputStream = null;
try {
inputStream = Resources.getResourceAsStream(resource);
// 创建sqlSessionFactory
sqlSessionFactory= new SqlSessionFactoryBuilder().build(inputStream);
} catch (IOException e) {
e.printStackTrace();
}
}
@Test
public void test01() {
// 通过sqlSessionFactory获取数据库的会话
SqlSession sqlSession = sqlSessionFactory.openSession();
// 用来接收查询结果的pojo类
Emp empByEmpno = null;
try {
// 获取要调用的接口类
EmpDao mapper = sqlSession.getMapper(EmpDao.class);
// 调用方法开始执行
empByEmpno = mapper.findEmpByEmpno(7369);
} catch (Exception e) {
e.printStackTrace();
} finally {
sqlSession.close();
}
System.out.println(empByEmpno);
}
@Test
public void test02() {
SqlSession sqlSession = sqlSessionFactory.openSession();
EmpDao mapper = sqlSession.getMapper(EmpDao.class);
// 插入方法默认都是返回int,表示受影响的数据条数
int zhangsan = mapper.insertEmp(new Emp(1111, "zhangsan"));
System.out.println(zhangsan);
// 提交查询
sqlSession.commit();
// 关闭数据库链接
sqlSession.close();
}
@Test
public void test03() {
SqlSession sqlSession = sqlSessionFactory.openSession();
EmpDao mapper = sqlSession.getMapper(EmpDao.class);
// 更新方法默认都是返回int,表示受影响的数据条数
int zhangsan = mapper.updateEmp(new Emp(1111, "lisi"));
System.out.println(zhangsan);
// 提交查询
sqlSession.commit();
sqlSession.close();
}
@Test
public void test04() {
SqlSession sqlSession = sqlSessionFactory.openSession();
EmpDao mapper = sqlSession.getMapper(EmpDao.class);
// 删除方法默认都是返回int,表示受影响的数据条数
int zhangsan = mapper.deleteEmp(1111);
System.out.println(zhangsan);
// 提交查询
sqlSession.commit();
sqlSession.close();
}
}
4.2 使用注解实现mapper
EmpDaoAnnotation.java
package cn.tulingxueyuan.dao;
import cn.tulingxueyuan.bean.Emp;
import org.apache.ibatis.annotations.Delete;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;
public interface EmpDaoAnnotation {
@Select("select * from emp where id= #{id}")
public Emp findEmpByEmpno(Integer empno);
@Update("update emp set ename=#{ename} where id= #{id}")
public int updateEmp(Emp emp);
@Delete("delete from emp where id= #{id}")
public int deleteEmp(Integer empno);
@Insert("insert into emp(id,user_name) values(#{id},#{username})")
public int insertEmp(Emp emp);
}
也可以直接在mapper接口上使用注解,将要查询的sql语句直接写在注解中,这样就可以不编写对应的mapper.xml了