JavaEE——MyBatis持久层框架(1)

目录

    • 1. 什么是 MyBatis
    • 2. 传统 JDBC 编程的问题
      • 2.1 传统 JDBC 程序
      • 2.2 JDBC 编程问题总结如下
    • 3.MyBatis 架构
    • 4. MyBatis 开发前的准备
      • 4.1. 下载 jar 包
      • 4.2使用 maven 构建
      • 4.3. 准备配置文件
    • 5. MyBatis 示例程序
      • 5.1. 需求描述
      • 5.2. 表结构设计
      • 5.3. 搭建开发环境
      • 5.4. 任务 1:根据用户 id 查询用户
      • 5.5. 任务 2:根据用户名模糊查询用户

1. 什么是 MyBatis

MyBatis 是一款优秀的持久层框架,它支持定制化SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的JDBC代码和手动设置参数以及获取结果集。MyBatis 可以使用简单的 XML 或注解来配置和映射原生信息,将接口和Java的POJO(Plain Old Java Objects,普通的 Java对象)映射成数据库中的记录。

Mybatis 通过 xml 或注解的方式将要执行的各种 statement(statement、preparedStatemnt、CallableStatement)配置起来,并通过 java 对象和 statement 中的 sql 进行映射生成最终执行的 sql 语句,最后由 mybatis 框架执行 sql 并将结果映射成 PO(persisent object 持久对象)并返回。

MyBatis 中文官网网址是 http://www.mybatis.org/mybatis-3/zh/index.html,官网提供了大量的学习资源。
JavaEE——MyBatis持久层框架(1)_第1张图片

2. 传统 JDBC 编程的问题

既然 MyBatis 是持久层框架,而传统的持久层是由 JDBC 实现的,那么我们就来分析传统 JDBC 存在哪些问题。

2.1 传统 JDBC 程序

public static void main(String[] args) { 
  Connection connection = null; 
  PreparedStatement preparedStatement = null; 
  ResultSet rs = null; 
  try { 
    //加载数据库驱动 
    Class.forName("com.mysql.jdbc.Driver"); 
     //通过驱动管理类获取数据库链接 
    connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/dbname", 
      "root","root"); 
    //定义 sql 语句 
    String sql = "select * from userInfo where username = ?"; 
    //获取预处理 statement 
    preparedStatement = connection.prepareStatement(sql); 
    //为占位符赋值 
    preparedStatement.setString(1, "林冲"); 
    //向数据库发出 sql 语句,获取查询结果集 
    rs =preparedStatement.executeQuery(); 
    //遍历查询结果集 
    while(rs.next()){ 
      System.out.println(rs.getString("id"); 
      System.out.println(rs.getString("username"); 
    } 
  } catch (Exception e) { 
    e.printStackTrace(); 
  }finally{ 
    //释放资源 
    if(rs!=null){ 
      try { 
        rs.close(); 
      } catch (SQLException e) { 
        e.printStackTrace(); 
      } 
    } 
    if(preparedStatement!=null){ 
      try { 
        preparedStatement.close(); 
      } catch (SQLException e) { 
        e.printStackTrace(); 
      } 
    } 
    if(connection!=null){ 
      try { 
        connection.close(); 
      } catch (SQLException e) { 
        e.printStackTrace(); 
      } 
    } 
  } 
} 

上面的代码展示了传统的 JDBC 编程,传统的 JDBC 编程步骤如下:
1、 加载数据库驱动
2、 创建并获取数据库链接
3、 创建 statement 对象
4、 拼写 sql 语句
5、 设置 sql 语句中的占位符的值
6、 执行 sql 语句并获取结果
7、 对 sql 执行结果进行解析处理
8、 释放资源

2.2 JDBC 编程问题总结如下

1、 数据库连接的创建、释放频繁造成系统资源浪费从而影响系统性能,如果使用数据库连
接池可解决该问题。
2、 SQL 语句编写在 Java 代码中,这种硬编码造成代码不易维护,当 SQL 变动时需要修改 java
源代码。
3、 使用 preparedStatement 向占位符传参数存在硬编码,因为 SQL 语句的 where 条件中占
位符的个数可能会变化,修改 SQL 还要修改 Java 源代码,系统不易维护。
4、 对结果集解析存在硬编码,SQL 语句变化导致解析代码变化,系统不易维护。

3.MyBatis 架构

MyBatis 是优秀的持久层框架,它能够解决 JDBC 编程中存在的问题。接下来先了解MyBatis 的架构。
JavaEE——MyBatis持久层框架(1)_第2张图片

上图是 MyBatis 的整体架构,从上之下依次的作用如下:

  1. SqlMapConfig.xml,此文件作为 mybatis 的全局配置文件,配置了 mybatis 的运行环境等信息。mapper.xml 文件即 sql 映射文件,文件中配置了操作数据库的 sql 语句。此文件需要在 SqlMapConfig.xml 中加载。
  2. 通过 mybatis 环境等配置信息构造 SqlSessionFactory,即会话工厂。
  3. 由会话工厂创建 sqlSession,即会话,操作数据库需要通过 sqlSession 进行。
  4. mybatis 底层自定义了 Executor 执行器接口来操作数据库,Executor 接口有两个实现,一个是基本执行器、一个是缓存执行器。
  5. Mapped Statement 也是 mybatis 一个底层封装对象,它包装了 mybatis 配置信息及 sql映射信息等。mapper.xml 文件中一个 sql 对应一个 Mapped Statement 对象,sql 的 id 即是 Mapped statement 的 id。
  6. Mapped Statement 对 sql 执行输入参数进行定义,包括 HashMap、基本类型、PPOJO,Executor 通过 Mapped Statement 在执行 sql 前将输入的 java 对象映射至 sql 中,输入参数映射就是 jdbc 编程中对 preparedStatement 设置参数。
  7. Mapped Statement 对 sql 执行输出结果进行定义,包括 HashMap、基本类型、PPOJO,Executor 通过 Mapped Statement 在执行 sql 后将输出结果映射至 java 对象中,输出结果映射过程相当于 jdbc 编程中对结果的解析处理过程。

4. MyBatis 开发前的准备

4.1. 下载 jar 包

MyBatis 的 jar 包可以从 https://github.com/mybatis/mybatis-3/releases 下载,下载界面如下图所示:
JavaEE——MyBatis持久层框架(1)_第3张图片

4.2使用 maven 构建

若使用 maven 构建,pom.xml 的依赖如下:

 
 org.mybatis 
  mybatis 
  3.4.5 
 

4.3. 准备配置文件

创建 MyBatis 的配置文件 SqlMapConfig.xml 和映射配置文件 Mapper.xml,并将
Mapper.xml 加载到 SqlMapConfig.xml 中。

5. MyBatis 示例程序

5.1. 需求描述

开发一个在线订购商品的电子商务网站,包括用户管理、商品管理、订单管理。后续课程都基于该需求进行讲解。本章完成用户管理模块,具体业务包括

  1. 根据用户 id 查询用户
  2. 根据用户名模糊查询用户
  3. 添加用户
  4. 更新用户
  5. 删除用户

5.2. 表结构设计

创建数据库命名为 eshop,创建用户表命名为 userinfo,创建商品表命名为 goods,创建订单表命名为 orders,创建订单明细表命名为 orderdetail。

CREATE DATABASE eshop; 
USE eshop; 
#创建用户表 
CREATE TABLE userinfo ( 
    id INT NOT NULL AUTO_INCREMENT PRIMARY KEY, 
    userName VARCHAR(20) NOT NULL ,            #用户名称 
    userPass VARCHAR(48) NOT NULL,              #用户密码 
    birthday DATE DEFAULT NULL ,                    #生日 
    gender CHAR(1) DEFAULT NULL ,                  #性别 
    address VARCHAR(256) DEFAULT NULL          #地址 
); 
#商品表 
CREATE TABLE goods ( 
  id INT AUTO_INCREMENT PRIMARY KEY, 
    goodsname VARCHAR(32),        #商品名称 
    price FLOAT(10,1),                    #商品定价 
    memo TEXT ,                            #商品描述 
    pic VARCHAR(200) ,                  #商品图片 
    createtime DATETIME              #生产日期 
); 
#订单表 
CREATE TABLE orders ( 
    id INT AUTO_INCREMENT PRIMARY KEY,#订单号 
    userid INT ,                                        #下单用户 id   
    createtime DATETIME NOT NULL,        #创建订单时间 
    memo VARCHAR(100)                        #备注 
); 
#外键:订单表 userid 引用 userinfo 的 id 
ALTER TABLE orders ADD CONSTRAINT fk_orders_userinfo_id FOREIGN KEY (userid)   
REFERENCES userinfo (id); 
#订单明细表 
CREATE TABLE orderdetail ( 
    id INT AUTO_INCREMENT PRIMARY KEY, 
    orderid INT NOT NULL ,                #订单 id 
    goodsid INT NOT NULL ,                #商品 id 
    itemsnum INT DEFAULT NULL        #商品购买数量 
); 
#外键:orderdetail 的 ordersid 引用表 orders 表的 id 
ALTER TABLE orderdetail ADD CONSTRAINT fk_orderdetail_orders_id FOREIGN KEY (orderid)   
REFERENCES orders (id); 
#外键:orderdetail 的 goodsid 引用表 goods 表的 id 
ALTER TABLE orderdetail ADD CONSTRAINT fk_orderdetail_goods_id FOREIGN KEY (goodsid)   
REFERENCES goods (id); 
#初始化用户数据 
INSERT INTO userinfo(userName,userPass,birthday,gender,address)   
VALUES('admin','admin','1980-10-10','男','陕西西安'); 
INSERT INTO userinfo(userName,userPass,birthday,gender,address)   
VALUES('林冲','lichong','1982-11-10','男','河南开封'); 
INSERT INTO userinfo(userName,userPass,birthday,gender,address)   
VALUES('扈三娘','husanniang','1981-03-10','女','山东聊城'); 
INSERT INTO userinfo(userName,userPass,birthday,gender,address)   
VALUES('孙二娘','sunerniang','1979-03-10','女','山东曾头市'); 
#初始化商品数据 
INSERT INTO goods(goodsname,price,memo,pic,createtime) 
VALUES('平谷大桃','33.6','产自河北','pinggudatao.jpg','2017-12-12 12:12:12'); 
INSERT INTO goods(goodsname,price,memo,pic,createtime) 
VALUES('油桃','17.6','产自河北','youtao.jpg','2017-12-13 12:12:12'); 
INSERT INTO goods(goodsname,price,memo,pic,createtime) 
VALUES('水蜜桃','39.6','产自河北','shuimitao.jpg','2017-12-14 12:12:12'); 
INSERT INTO goods(goodsname,price,memo,pic,createtime) 
VALUES('蟠桃','33.6','产自河北','pantao.jpg','2017-12-11 12:12:12'); 
INSERT INTO goods(goodsname,price,memo,pic,createtime) 
VALUES('毛桃','31.6','产自河北','maotao.jpg','2017-10-12 12:12:12'); 
INSERT INTO goods(goodsname,price,memo,pic,createtime) 
VALUES('樱桃','43.6','产自河北','yingtao.jpg','2017-11-22 12:12:12'); 
#初始化一条订单 
INSERT INTO orders(userid,createtime,memo)   
VALUES(2,'2017-09-21 16:26:51','要新鲜的'); 
#初始化订单明细 
INSERT INTO orderdetail(orderid,goodsid,itemsnum)VALUES(1,1,3); 
INSERT INTO orderdetail(orderid,goodsid,itemsnum)VALUES(1,2,2); 
#初始化一条订单 
INSERT INTO orders(userid,createtime,memo)   
VALUES(2,'2017-09-22 16:26:50','和上次的一样'); 
#初始化订单明细 
INSERT INTO orderdetail(orderid,goodsid,itemsnum)VALUES(2,1,3); 
INSERT INTO orderdetail(orderid,goodsid,itemsnum)VALUES(2,2,2); 

5.3. 搭建开发环境

第一步:创建 maven 项目
在 IDEA中创建 maven-archetype-quickstart 项目,groupId 为 cn.itlaobing,artifactId 为mybatis,version 为 0.0.1-SNAPSHOT。

第二步:设置 pom.xml 文件

<dependencies> 
  <dependency> 
    <groupId>junitgroupId> 
    <artifactId>junitartifactId> 
    <version>4.10version> 
    <scope>testscope> 
  dependency> 
   
  <dependency> 
    <groupId>org.mybatisgroupId> 
    <artifactId>mybatisartifactId> 
    <version>3.4.5version> 
  dependency> 
   
  <dependency> 
   <groupId>mysqlgroupId> 
    <artifactId>mysql-connector-javaartifactId> 
    <version>5.1.43version> 
  dependency> 
   
  <dependency> 
    <groupId>log4jgroupId> 
    <artifactId>log4jartifactId> 
    <version>1.2.17version> 
  dependency> 
dependencies> 

第三步:创建 MyBatis 配置文件
在项目中创建 Source Folder,命名为 src/main/resources,在该目录下创建 MyBatis 配置文件,命名为 SqlMapConfig.xml(该配置文件的文件名可自定义,建议使用 SqlmapConfig.xml),具体配置如下:

 
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/eshop?characterEncoding=utf8" /> 
        <property name="username" value="root" /> 
        <property name="password" value="root" /> 
      dataSource> 
    environment> 
  environments> 
   
  <mappers> 
    <mapper resource="userInfo.xml" /> 
  mappers> 
configuration> 

第四步:创建映射文件
在 src/main/resources 目录下创建映射文件,命名为 userinfo.xml,具体配置如下:

 
DOCTYPE mapper 
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" 
"http://mybatis.org/dtd/mybatis-3-mapper.dtd"> 
 
<mapper namespace="eshop"> 

mapper> 

第五步:创建日志文件
在 src/main/resources 目录下创建日志文件 log4j.properties,将日志级别设置为 debug,具体配置如下:

log4j.rootLogger=debug,stdout,file 
log4j.appender.stdout=org.apache.log4j.ConsoleAppender 
log4j.appender.stdout.Target=System.out 
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout 
log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n 
 
log4j.appender.file=org.apache.log4j.RollingFileAppender 
log4j.appender.file.MaxFileSize=1MB 
log4j.appender.file.MaxBackupIndex=1024 
log4j.appender.file.File=c\:/eshop.html 
log4j.appender.file.layout=org.apache.log4j.HTMLLayout 
log4j.appender.file.layout.ConversionPattern=%d{yyyy-MM-dd HH\:mm\:ss} 
[%c]-[%-5p] %m%n%n 

5.4. 任务 1:根据用户 id 查询用户

第一步:创建 POJO 类
POJO(Plain Ordinary Java Object, 普通 JAVA 对象,只有属性及其 set/get 方法),也就是实体类。在 src/main/java 目录中创建包 com.xawl.mybatis.model,在该包下创建 POJO 类UserInfoModel,代码如下:

public class UserInfoModel implements Serializable{ 
  private int id; 
  private String userName; 
  private String userPass; 
  private Date birthday; 
  private String gender; 
  private String address; 
  @Override 
  public String toString() { 
    return this.id+"--"+this.userName +"--"+this.userPass+"--"+this.birthday+ 
    "--"+this.gender+"--"+this.address; 
     } 
  省略 get/set 方法 
} 

第二步:配置 SQL 映射
在 userInfo.xml 映射配置文件中创建 Mapped Statement ID,实现根据用户 id 查询用户

 
DOCTYPE mapper 
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" 
"http://mybatis.org/dtd/mybatis-3-mapper.dtd"> 
 
<mapper namespace="eshop"> 
   
   
  <select id="findUserInfoById" parameterType="int" 
    resultType="cn.itlaobing.mybatis.model.UserInfoModel"> 
    SELECT * FROM userInfo WHERE id=#{id} 
  select> 
mapper> 

第三步:创建单元测试类
在 src/test/java 目录中创建包 com.xawl.mybatis.test,在该包下创建单元测试类
TestEshop,代码如下:

package cn.itlaobing.mybatis.test; 
import java.io.IOException; 
import java.io.InputStream; 
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 cn.itlaobing.mybatis.model.UserInfoModel; 
public class TestEshop { 
 //  定义会话工厂对象 sqlSessionFactory 
  private SqlSessionFactory sqlSessionFactory = null; 
  @Before 
  public void setUp() throws IOException { 
    //  加载 MyBatis 配置文件 
    String resource = "SqlMapConfig.xml"; 
    InputStream inputStream = Resources.getResourceAsStream(resource); 
    //  创建会话工厂,传入 mybatis 的配置文件信息 
    sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); 
  } 
  @Test 
  public void testFindUserInfoById() { 
    //  通过会话工厂得到 SqlSession 对象 
    SqlSession sqlSession = sqlSessionFactory.openSession(); 
    /* 
      *  通过 SqlSession 操作数据库,sqlSession.selectOne 用于从库中查出一条记录 
      *  第一个参数:Mapped Statement ID,其值为 namespace+"."+id   
      *  第二个参数:指定和映射文件中所匹配的 parameterType 类型的参数 
      *  返回值:sqlSession.selectOne 的返回值与映射文件中 resultType 类型的值一致 
      */ 
    UserInfoModel model = sqlSession.selectOne("eshop.findUserInfoById", 1); 
    //  关闭 sqlSession 对象 
    sqlSession.close(); 
    System.out.println(model.getId() + "-" + model.getUserName()); 
  } 
}

第四步:单元测试
运行单元测试,控制台输出日志如下

log4j:WARN No such property [conversionPattern] in org.apache.log4j.HTMLLayout. 
12:20:20,852 DEBUG LogFactory:135 - Logging initialized using 'class 
org.apache.ibatis.logging.log4j.Log4jImpl' adapter. 
12:20:20,977 DEBUG PooledDataSource:335 - PooledDataSource forcefully closed/removed all connections. 
12:20:20,977 DEBUG PooledDataSource:335 - PooledDataSource forcefully closed/removed all connections. 
12:20:20,977 DEBUG PooledDataSource:335 - PooledDataSource forcefully closed/removed all connections. 
12:20:20,977 DEBUG PooledDataSource:335 - PooledDataSource forcefully closed/removed all connections. 
12:20:21,055 DEBUG JdbcTransaction:137 - Opening JDBC Connection 
12:20:21,289 DEBUG PooledDataSource:406 - Created connection 811587677. 
12:20:21,305 DEBUG JdbcTransaction:101 - Setting autocommit to false on JDBC Connection 
[com.mysql.jdbc.JDBC4Connection@305fd85d] 
12:20:21,305 DEBUG findUserInfoById:159 - ==> Preparing: SELECT * FROM userInfo WHERE id=?   
12:20:21,336 DEBUG findUserInfoById:159 - ==> Parameters: 1(Integer) 
12:20:21,352 DEBUG findUserInfoById:159 - <==       Total: 1 
12:20:21,352 DEBUG JdbcTransaction:123 - Resetting autocommit to true on JDBC Connection 
[com.mysql.jdbc.JDBC4Connection@305fd85d] 
12:20:21,367 DEBUG JdbcTransaction:91 - Closing JDBC Connection 
[com.mysql.jdbc.JDBC4Connection@305fd85d] 
12:20:21,367 DEBUG PooledDataSource:363 - Returned connection 811587677 to pool. 
1--admin--admin--1980-10-10----陕西西安 
  1. 日志中输出了执行的 sql 语句为 SELECT * FROM userInfo WHERE id=?,占位符?的值来自于映射文件中的#{id}的值。
  2. 传入参数是:Parameters: 1(Integer) ,其中的 1 是输入参数,它会被 MyBatis传入到映射文件中的#{id}中。
  3. 输出结果是:Total:1,其值的 1 表示查询出 1 条数据。
  4. sqlSession.selectOne()返回单条记录对应的 POJO 对象。
  5. sqlSession.selectOne(“eshop.findUserInfoById”, 1)与映射文件之间的关系如下图所示
    JavaEE——MyBatis持久层框架(1)_第4张图片

5.5. 任务 2:根据用户名模糊查询用户

第一步:创建 Mapped Statement ID
在映射文件 userinfo.xml 中添加 Mapped Statement ID,代码如下:

<mapper namespace="eshop"> 
   
  <select id="findUserInfoByName" parameterType="java.lang.String"   
    resultType="cn.itlaobing.mybatis.model.UserInfoModel"> 
    SELECT * FROM userInfo WHERE userName LIKE '%${value}%' 
  select> 
mapper> 

第二步:编写单元测试
在 TestEshop 单元测试类中添加单元测试方法,代码如下:

@Test 
public void testFindUserInfoByName() { 
  SqlSession sqlSession = sqlSessionFactory.openSession(); 
  List<UserInfoModel> list = sqlSession.selectList("eshop.findUserInfoByName","娘"); 
  sqlSession.close(); 
  System.out.println(list); 
} 

第三步:单元测试
运行单元测试,控制台输出日志如下

14:24:21,341 DEBUG findUserInfoByName:159 - ==>  Preparing: SELECT * FROM userInfo WHERE 
userName LIKE '%娘%'   
14:24:21,372 DEBUG findUserInfoByName:159 - ==> Parameters:   
14:24:21,419 DEBUG findUserInfoByName:159 - <==       Total: 2 
14:24:21,419 DEBUG JdbcTransaction:123 - Resetting autocommit to true on JDBC Connection 
[com.mysql.jdbc.JDBC4Connection@26a7b76d] 
14:24:21,419 DEBUG JdbcTransaction:91 - Closing JDBC Connection 
[com.mysql.jdbc.JDBC4Connection@26a7b76d] 
14:24:21,419 DEBUG PooledDataSource:363 - Returned connection 648525677 to pool. 
[3--扈三娘--husanniang--1981-03-10--女--山东聊城, 4--孙二娘--sunerniang--1979-03-10--女--山东曾头
市] 
  1. 日志中输出了执行的 sql 语句为 SELECT * FROM userInfo WHERE userName LIKE ‘%娘%’。
  2. 传入参数是:Parameters:
  3. 输出结果是:Total:2,其值的 2 表示查询出 2 条数据。
  4. sqlSession.selectList()返回多条记录对应的 List 泛型集合类型。 为 Mapped Statement ID 中的 SQL 语句传入参数时,可以使用#{}占位符,也可以使用${}
    占位符,两者区别如下:
    #{ }:被解析为一个 JDBC 预编译语句(prepared statement)的参数占位符。
    例如: select * from userInfo where userName = #{ userName };
    动态解析为:
    select * from userInfo where userName = ?;
    因此#{}可以防止 SQL 注入 $ {} :被解析为 sql 拼接符号
    例如: select * from userInfo where userName = ‘$ { userName }’; 当我们传递的参数为"admin"时,上述 sql 的解析为 select * from userInfo where userName =‘admin’; 因此${}无法防止 SQL 注入

你可能感兴趣的:(MyBaits,java)