三大框架学习之-mybatis基础一.使用JDBC连接

一.使用JDBC连接数据库的一些问题

  • 咱们先来看一段使用JDBC的代码

image.png

  • 通过上面的代码,我们可以看出一些问题
    1. 使用JDBC,sql语句是写在JAVA代码中,想要修改sql语句必须修改代码,耦合度太高

    2. 需要手动设置参数,如果有查询结果还需要手动转换结果,不够面向对象

    3. 需要手动关闭连接池资源,麻烦

    4. 代码量多,重复代码过多

mybatis可以解决上述问题

二.什么是mybatis

  • MyBatis是一个半ORM的数据库持久化框架;
    • 框架:就是一个半成品的工具,它是由一堆jar包组成。所以要使用框架,第一步就是导入jar包

    • 持久化:就是将对象数据,保存到数据库当中,从而达到断电也存在的目的,叫做持久化

    • ORM:对象关系映射(Object Relational Mapping,简称ORM):是一种为了解决面向对象与关系数据库存在的互不匹配的现象的技术;

    • 半ORM(半映射):不是通过完整的映射方式,需要自己写sql语句。

    • 全ORM框架(全映射):JPA、Hibernate

图片1.png

  • Mybatis底层还是原生的JDBC代码,对JDBC代码的封装;

    • JDBC说简单点就是java连接数据库的唯一技术,所以Mybatis其实就是封装了JDBC,让我们使用更简单
  • Mybatis起源:可通过百度百科了解

  • Mybatis优势:
    - sql语句从java代码中抽取出来,方便维护,修改sql时不需要修改java代码
    - 参数与结果集可通过对象的方式,mybatis自动帮我们解析、绑定
    - mybatis与spring框架整合之后,不必手动关闭资源

三.Mybatis入门

- 准备

  • 想要使用框架,第一步就是导入jar包

image.png

  • 其他步骤:
    • 准备相应的测试数据库、测试表单、测试数据

    • 准备数据库表相应的domain

    • 准备实体类相应的dao层接口以及空实现

    • 准备测试包、测试类

package cn.itsource.domain;

import java.math.BigDecimal;

public class Product {
	
	// id
	private Long id;
	
	// 商品名称
	private String productName;

	// 品牌
	private String brand;
	
	// 供应商
	private String supplier;
	
	
	// 零售价
	private BigDecimal salePrice;
	
	// 进价
	private BigDecimal costPrice;
	
	// 折扣价
	private Double cutoff;
	
	// 商品分类编号
	private Long dir_id;

	public Product() {
		super();
		// TODO Auto-generated constructor stub
	}

	public Product(Long id, String productName, String brand, String supplier, BigDecimal salePrice,
			BigDecimal costPrice, Double cutoff, Long dir_id) {
		super();
		this.id = id;
		this.productName = productName;
		this.brand = brand;
		this.supplier = supplier;
		this.salePrice = salePrice;
		this.costPrice = costPrice;
		this.cutoff = cutoff;
		this.dir_id = dir_id;
	}

	public Long getId() {
		return id;
	}

	public void setId(Long id) {
		this.id = id;
	}

	public String getProductName() {
		return productName;
	}

	public void setProductName(String productName) {
		this.productName = productName;
	}

	public String getBrand() {
		return brand;
	}

	public void setBrand(String brand) {
		this.brand = brand;
	}

	public String getSupplier() {
		return supplier;
	}

	public void setSupplier(String supplier) {
		this.supplier = supplier;
	}

	public BigDecimal getSalePrice() {
		return salePrice;
	}

	public void setSalePrice(BigDecimal salePrice) {
		this.salePrice = salePrice;
	}

	public BigDecimal getCostPrice() {
		return costPrice;
	}

	public void setCostPrice(BigDecimal costPrice) {
		this.costPrice = costPrice;
	}

	public Double getCutoff() {
		return cutoff;
	}

	public void setCutoff(Double cutoff) {
		this.cutoff = cutoff;
	}

	public Long getDir_id() {
		return dir_id;
	}

	public void setDir_id(Long dir_id) {
		this.dir_id = dir_id;
	}

	@Override
	public String toString() {
		return "Product [id=" + id + ", productName=" + productName + ", brand=" + brand + ", supplier=" + supplier
				+ ", salePrice=" + salePrice + ", costPrice=" + costPrice + ", cutoff=" + cutoff + ", dir_id=" + dir_id
				+ "]";
	}
	
}

复制代码

- 入门实现步骤分析

  • 实现步骤分析

    • 代码的准备已经就绪了,那么接下来就可以开始使用了

    • 学习来源:

      1.官方网站: mybatis.org/mybatis-3/z…

      2.官方文档

  • 要想使用Mybatis,其实最关键的第一步就是拿到Mybatis的核心对象SqlSessionFactory,那么我们先拿到这个核心对象

  • 在文档中或者官方入门案例中,已经告诉我们如何拿到这个核心对象,我们来一步一步解析

    • 要获取SqlSessionFactory,我们需要准备一个核心的Mybatis-config.xml文件,然后通过SqlSessionFactoryBuilder来创建,所以步骤:

      (1)创建Mybatis核心配置文件(Mybatis-config.xml),并配置数据库环境;

      (2)加载核心配置文件;

      (3)创建一个SqlSessionFactoryBuilder对象;

      (4)通过SqlSessionFactoryBuilder对象构建一个SqlSessionFactory对象;

      (5)创建Mapper映射文件,并配置;

      (6)通过SqlSessionFactory获取SqlSession执行映射SQL;

- 入门实现:

  • 配置核心配置文件: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>
	<!-- 环境们 (很多环境的意思)
		default:默认使用哪一个环境(必需对应一个环境的id)
	 -->
	<environments default="development">
		<!-- 一个环境  id:为这个环境取唯一一个id名称 -->
		<environment id="development">
		<!-- 事务管理   type:JDBC(支持事务)/MANAGED(什么都不做) -->
		<transactionManager type="JDBC" />
		<!-- 数据源, 连接池  type(POOLED):MyBatis自带的连接池 -->
		<dataSource type="POOLED">
		<!-- 连接数据库的参数:直接写死的方式 -->
			<property name="driver" value="com.mysql.jdbc.Driver" />
			<property name="url" value="jdbc:mysql:///mydb" />
			<property name="username" value="root" />
			<property name="password" value="admin" />     
	</environments>
   <!-- 这个mappers代表的是相应的ORM映射文件 -->
	<mappers> 
		<mapper resource="cn/itsource/dao/ProductMapper.xml" /> 
	</mappers> 
</configuration> 
复制代码
  • 准备映射文件:
    • 我们的映射文件(也称之为mapper文件)一般情况下是和它对应的dao接口在同一个包下;

    • 这个映射文件的名称一般叫做 XxxMapper.xml (Xxx代表的是实体类名称)

      • 例如

        • 实体类有:
          cn.itsource.domain.Product
        • 映射文件名为:
          cn/itsource/dao/ProductMapper.xml

        namespace的名称通常是接口的完全限定名;

        除了MyBatis支持的类型,其它的类型都通通使用全限定

<?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的主要功能就是写sql
	 mapper:根
	 namespace:命令空间 (用来确定唯一)
     namespace的值:接口的完全限定名
 -->
<mapper namespace="cn.itsource.dao.ProductDao">
	<!-- 
		select :这里面写查询语句
		id:用来确定这条sql语句的唯一
			   以后我们确定唯一,也就是找sql语句 : namespace + id
			 例: cn.itsource.mybatis.day1._1_hello.IProductDao.get
		parameterType : 传入的参数类型  long:大Long  _long:小long (具体的对应请参见文档)
		resultType : 结果类型(第一条数据返回的对象类型)自己的对象一定是完全限定类名
	 -->
	<select id="get" parameterType="long" resultType="cn.itsource.domain.Product">
		select * from product where id = #{id}
	</select>
</mapper> 
复制代码
  • 入门代码:
    // 查询所有方法
    @Test
    public List<Product> findAll() {
        SqlSession sqlSession = null;
        try {
            //1 准备配置文件 ok
            //2 创建SqlSessionFactory
            Reader reader = Resources.getResourceAsReader("MyBatis-Config.xml");
            SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
         
            //3 获取sqlSession做操作
            sqlSession = sqlSessionFactory.openSession();

            //表示调用那句sql(namespace+.+id)-拷贝,传入参数,接收得到返回值
            return  sqlSession.selectList("cn.itsource.dao.ProductDao.findAllProduct");

        }catch (Exception e){
            e.printStackTrace();
        }finally {
            if (sqlSession != null) {
                sqlSession.close();
            }
        }
        return null;
    }
}

// 其他增删改查方法...

复制代码

四.Mybatis工具类抽取

- 为什么要抽取MybatisUtil工具类:

  • 其实每个Dao的方法都要获取SQLSession,并且使用完都要关闭,对于像获取和关闭等操作都应该交给工具处理;

- 抽取思路分析

  • 对于获取核心配置文件流以及获取核心对象,我们只需要获取一次,那么我们可以利用静态代码块的特性,写在静态代码块中,随着类的加载而加载一次

  • 使用枚举类的方式创建工具类最为简单

  • 因为每个线程都应该有它自己的SqlSession实例,SqlSession的实例是不能被共享的,也是线程不安全的,所以提供一个获取SqlSession的方法,让我们每次需要SqlSession时都是获取的一个新的

注意:打开了SqlSession连接,一定要关闭,关闭很重要

- 抽取实现

package cn.itsource.util;

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;

public enum MybatisUtil {
	INSTANCE;
	private static SqlSessionFactory sqlSessionFactory;
	
	static{
		try {
			InputStream resourceAsStream = Resources.getResourceAsStream("mybatis-config.xml");
			sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
		} catch (IOException e) {
			e.printStackTrace();
		}
		
	}
	
	
	public SqlSession getSqlSession() {
		SqlSession sqlSession = sqlSessionFactory.openSession();
		return sqlSession;
	}

}
复制代码

- 使用工具类完成CRUD

五.SQL映射器Mapper

- 概述

  • 之前我们通过Mybatis操作数据库的时候,我们除了写接口,还写了实现类,但是其实Mybatis通过动态代理机制给我们提供了实现,让我们无需自己再写实现类

  • 传统的接口统一以dao结尾,但是Mybatis推荐规范不是以dao为后缀,而是以mapper为后缀,例如 IProductDao -> ProductMapper。并且包名称为mapper

    • sql语句配置文件需与mapper接口类在同一个包下

- 映射器介绍

  • 刚才我们在获取到sql语句配置文件中的方法时是使用的namespace加上sql语句id获取,那么其实Mybatis给我们提供了mapper字节码映射的方式。

  • 映射器实现步骤:

    • 根据需求,创建模型相关的Mapper接口,例如:ProductMapper;

    • 在同包下编写映射文件,例如:ProductMapper.xml;

      • Mapper映射文件的命名空间,必须和接口的"完全限定名"一致;
      • 定义sql标签的id,必须和"接口的方法名"一致;
    • 在Mybatis核心配置文件中配置(或注册)Mapper映射文件;

    • 测试

      友情提示:最好不要在mapper接口中使用方法重载,因为sql标签的id即为方法名,写重载方法容易出问题;

- 两种实现方式:

  1. 传统实现方式:

image.png

  1. 映射器实现方式:其实就是将我们想要执行的接口的字节码文件交给Mybatis,让它帮我们创建出实体类,这样我们就可以直接使用实体类调用方法。
    • 原理:Mybatis会通过动态代理模式帮助我们创建出实体类,我们在调用实体类的时候,其实Mybatis会根据我们在核心配置文件中加入的mapper去找到我们的实体类,并且根据全限定名以及接口名称定位到SQL语句,并执行

image.png

六.Mybatis使用细节

- 添加时返回自增长的ID

  • 概述:在JDBC中我们知道了插入数据时我们是可以获取到自增长的主键ID的,那么在Mybatis中我们如何获取呢?

  • 两种方式:

第一种:

- useGeneratedKeys:true/false:这个属性的意思就是,是否开启返回自增长的ID

- keyProperty:这个属性的意思就是自增长的ID返回之后,放在你插入的对象中哪一个字段

- keyColumn:数据库中的主键id是哪个,如果主键是第一列那么可以省略不写,如果不是第一列那么必须写
    
复制代码
XxxMapper.xml写法:
<!-- 插入数据并返回自增ID
有自增ID功能数据库可以采用useGeneratedKeys="true"开启判断是否是自增ID
keyProperty="id"  指定插入数据后自增ID返回时赋值给实体类的那个属性(这里是id属性)
-->
<!-- void insert1(Product product) -->
<insert id="insert"  useGeneratedKeys="true" keyProperty="id">
	insert into product(productName) values(#{productName})
</insert>
复制代码

第二种:现在已经不使用了

XxxMapper.xml写法:
<!-- void insert2(Product product) -->
<insert id="insert2">
	<!-- 将插入数据的主键返回,返回到product对象中
	 	SELECT LAST_INSERT_ID():得到刚insert进去记录的主键值,只适用于自增主键
	 	keyProperty:将查询到主键值设置到parameterType指定的对象的那个
	 	resultType:指定SELECT LAST_INSERT_ID()的结果类型selectKey中resultType属性指定期望主键的返回的数据类型,
	 	keyProperty属性指定实体类对象接收该主键的字段名
	 	order属性指定执行查询主键值SQL语句是在插入语句执行之前还是之后(可取值:after和before)
         注:mysql用after,oracle用before
	 -->
	<selectKey keyProperty="id" order="AFTER" resultType="java.lang.Long">
	 	SELECT LAST_INSERT_ID()	
	 </selectKey>
	insert into product(productName) values(#{productName})
</insert>
复制代码

- Mybatis运行日志

  • 为什么需要日志:
    • 我们以前看控制台日志是使用的System.out.println()的方式,但是这种方式打印日志是无法打印运行时日志的,也就是Mybatis的sql语句。比如我们写一条插入语句,我们不知道这条语句在执行时是什么样子,所以我们需要日志帮助我们在控制台或者文件中显示出来
  • 常见的日志框架:
    • System.out.print("xxxx")

    • 写一个日志框架

      • dblLog.jar(yhptest)
      • Log log = LogFactory.getLog(this.class)
      • log.error/Info/warn...
    • 定义标准

      • 新写的日志框架按照日志标准来做;
      • 以前的日志框架使用适配器来做适配;
    • 标准:slf4j(标准) -- commons.logging,log4j(实现)

    • 最常用的日志框架就是Log4j日志框架

      • Log4j主要用于日志信息的输出。可以将信息分级别(严重fatal、错误error、警告warn、调式debug、信息info)按不同方式(控制台、文件、数据库)和格式输出;

      • 实现步骤:

        • 导入log4j的jar包(3个);
        • 在配置文件resources下添加log4j.properties【名字不能改】;
      • Log4j主要组成:

        • 日志器(Logger):负责消息输出,提供了各种不同级别的输出方法;
        • 输出器(Appender):负责控制消息输出的方式,例如输出到控制台、文件输出等;
        • 布局器(格式器,Layout):负责控制消息的输出格式;
      • Log4j配置文件配置的几种方式:此次我们使用控制台输出+自定义布局

#日志器logger #輸出器appender #布局器layout
#1.控制台输出
#指定日志器的输出级别和日志器的名称
#log4j.rootLogger=info,myconsole
#指定输出器
#log4j.appender.myconsole=org.apache.log4j.ConsoleAppender
#指定布局器
#log4j.appender.myconsole.layout=org.apache.log4j.SimpleLayout

#2.文件输出.txt
#指定日志器的输出级别和日志器的名称
#log4j.rootLogger=error,myfile
#指定输出器
#log4j.appender.myfile=org.apache.log4j.FileAppender
#log4j.appender.myfile.File=E:\\log4j.txt
#指定布局器(普通布局表示文本输出)
#log4j.appender.myfile.layout=org.apache.log4j.SimpleLayout

#3.文件输出.html
#指定日志器的输出级别和日志器的名称
#log4j.rootLogger=error,myhtml
#指定输出器
#log4j.appender.myhtml=org.apache.log4j.FileAppender
#log4j.appender.myhtml.File=D:\\log4j.html
#指定布局器(网页布局)
#log4j.appender.myhtml.layout=org.apache.log4j.HTMLLayout


#4.控制台输出+文件输出.txt
#指定日志器的输出级别和日志器的名称
#log4j.rootLogger=error,con,file
#指定输出器
#log4j.appender.con=org.apache.log4j.ConsoleAppender
#log4j.appender.file=org.apache.log4j.FileAppender
#log4j.appender.file.File=D\:\\log4j.txt
#指定布局器(网页布局)
#log4j.appender.con.layout=org.apache.log4j.SimpleLayout
#log4j.appender.file.layout=org.apache.log4j.SimpleLayout


#5.控制台输出+自定义布局
log4j.rootLogger=DEBUG,my
#指定输出器
log4j.appender.my=org.apache.log4j.ConsoleAppender
#指定布局器(自定义布局)
#指定布局为自定义布局
log4j.appender.my.layout=org.apache.log4j.PatternLayout
#指定在自定义布局的格式,%d -- 表示当前系统时间,%t -- 执行该业务的线程名称,%p -- 日记器的级别,-5 -- 5表示输出字符的个数,符号表示右对齐
#%c -- 表示指定业务所在的类的完全限定名(包名.类名),%m -- 输出额外信息,%n -- 表示换行
log4j.appender.my.layout.ConversionPattern=%d [%t] %-5p [%c] - %m%n
#设置package(可以是自定义的包也可以是api的包)输出级别
log4j.logger.org.springframework=info
log4j.logger.cn.itsource=debug
复制代码

- MyBatis中使用别名

  • 在sql的xml中,我们的返回值如果是我们自己的类对象,那么需要写上类的全限定名称,那么为什么参数类型的long的时候不需要呢?

  • 在Mybatis中有一些内置别名

image.png

  • 除了Mybatis给我们提供的内置别名,我们也可以给我们自己的实体类取别名,需要再Mybatis核心配置文件中配置

方式一:给某一个实体类取别名,单个配置

<typeAliases>
	<!-- 单个配置:测试时使用 -->
	<typeAlias type="cn.itsource.domain.Product" alias="Product" />
</typeAliases>
复制代码

方式二:给某个包下面的所有类配置别名,包配置

<typeAliases>
	<!-- 包的配置:项目中使用,添加了包之后,类名或类名首字母小写就是别名 -->
	<package name="cn.itsource.domain" />
</typeAliases>
复制代码

- #与$区别 & OGNL表达式

  • #{}

    • 在Mybatis中如果使用 #的方式,可以有效防止sql注入,因为用#会替换为?,最终通过PreparedStament来设置参数,不会有sql注入问题;

    • 多数情况下都推荐使用#方式

  • ${}

    • 在Mybatis中 $ 符号会原样展示值得内容,此方式有sql注入的风险
    • 注意:除了要拼接sql结构体要用$(是直接拼接sql),其他都用#
  • OGNL表达式

    • 其实我们使用的#{},${}这种方式有一个名称为OGNL表达式,在页面上它又叫EL表达式

  • 使用OGNL表达式改造四大属性

    • 我们可以在官方文档看到,官方在配置文件中配置四大属性时是通过OGNL表达式获取的四大属性,所以我们也按照官方推荐的方式来获取连接属性
  • 使用步骤:

    • 改造Mybatis核心配置文件,将连接属性设置为使用属性文件的方式
<?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>

    <!-- 引入配置文件信息,这里不能加classpath:。
		resource:引入类路径下的资源,即classpath,所以不需要写classpath:
		url:引入网络路径或磁盘路径下的资源 
	-->
        <!-- 如果使用属性文件的方式,需要引入属性文件 -->
	<properties resource="db.properties"></properties>
	
	<!-- 别名标签 -->
	<typeAliases>
	<!-- 配置给那个包下面的实体类取别名,别名就是类名或者是类名首字母小写 -->
		<package name="cn.itsource.domain"/>
	</typeAliases>
	
	
	<!-- 环境们 (很多环境的意思)
		default:默认使用哪一个环境(必需对应一个环境的id)
	 -->
	<environments default="development">
		<!-- 一个环境  id:为这个环境取唯一一个id名称 -->
		<environment id="development">
		<!-- 事务管理   type:JDBC(支持事务)/MANAGED(什么都不做) -->
		<transactionManager type="JDBC" />
		<!-- 数据源, 连接池  type(POOLED):MyBatis自带的连接池 -->
		<dataSource type="POOLED">
                <!-- 连接数据库的参数:使用属性文件的方式 -->
                    <property name="driver" value="${db.driver}" />
                    <property name="url" value="${db.url}" />
                    <property name="username" value="${db.username}" />
                    <property name="password" value="${db.password}" />
		</dataSource>
		</environment>
	</environments>
   <!-- 这个mappers代表的是相应的ORM映射文件 -->
	<mappers> 
		<mapper resource="cn/itsource/dao/ProductMapper.xml" /> 
		<mapper resource="cn/itsource/mapper/ProductMapper.xml" /> 
	</mappers> 
</configuration> 
复制代码
  • 在resources下添加属性文件 db.properties
db.driver=com.mysql.jdbc.Driver
db.url=jdbc:mysql:///test0303
db.username=root
db.password=admin
复制代码
  • 执行查询测试,测试属性文件方式是否配置成功

Mybatis基础就已经完成了