0. 相关概念
0.1 框架和工具类
- 工具类
- 对程序中一小段代码的封装。项目中大多数代码还是需要我们写。
- 框架
- 通俗理解框架
- 可以看做一个半成品的软件/项目。使用框架开发项目,项目中半数以上代码就不需要 我们编写了。
- 我们一般需要配置(大多数框架都是重配置轻编码的)+少量编码,就可完成项目中的需求。
- 框架的目的 就是为了简化编码:
eg:Mybatis
。
我们在学习完Mybatis
之后,dao
层会更简单。你只需要写一个dao
接口,然后写一个SQL
语句,dao
层就已经写完了。
- 学习步骤
- 这个框架能干什么?
- 导包+配置
- 框架少量的
API
,少量编码
0.2 ORM
-
Object Relational Mapping
对象关系映射的思想 -
对象 - Java中的对象 关系-关系型数据库中表的记录 映射 - 一对一关联起来
-
java
项目中的每一个实体类,对应数据库中的一个表;
java
类中的属性,对应数据库表中的字段。
java
类中的一个实体对象,对应一个数据表中的一条记录 -
全自动
ORM
框架:hibernate
。通过操作实体对象,就可以完成对数据库表中记录的操作。复杂业务性能低下,不可定制,学习成本高。
-
半自动的
ORM
框架:Mybatis
,ibatis
基于
ORM
,但是SQL
语句需要我们自己编写。自己优化SQL
,可定制性强,效率高。
0.3 Mybatis
&原生JDBC
-
原生jdbc
注册驱动,设置连接参数获取连接- 获取执行对象
- 设置
SQL
参数并执行SQL
语句 - 封装结果集并封装成实体对象
释放资源
相同的内容已经抽取到工具类中(上述删除线标识的步骤)
模板化(步骤不变,部分步骤相同,上述加粗的内容)的操作,也是可以抽取封装的。
但是相同的步骤中又有不同的地方,不同的地方如果也要实现解耦,需要通过配置来实现
-
Mybatis
解决方案- 连接复用:使用数据库连接池初始化并管理连接
- 解耦:将
SQL
语句和设置到SQL
中的参数抽取到xml
配置文件中 - 自动封装:通过反射内省等技术,实现查询结果集字段与实体属性自动映射并赋值
-
Mybatis
框架抽取后的样子 -
Mybatis
简介- 是一个数据持久层(
DAO
)框架,封装共性内容让使用者只关注SQL
本身。结合了ORM
思想,是一个ORM
半自动的框架。 - 使用
Mybatis
之后,我们只需要定义一个Dao
层接口+存储SQL
的配置文件(Mybatis
映射配置文件),就可以完成Dao
层内容。
- 是一个数据持久层(
1. Mybatis
快速入门
1.1 Mybatis
快速入门步骤
-
导入Jar包
mybatis-3.5.3.jar
mysql-connector-java-5.1.37-bin.jar
junit-4.12.jar
-
编写
Mybatis
核心配置文件:mybatis-config.xml
- 连接数据库四要素
<mapper>
标签中映射文件路径不是.
是/
建议复制,不要手敲
-
编写
POJO
类和Dao
层接口,初始化数据库表及数据 -
编写映射文件:
StudentDao.xml
,在映射文件中编写SQL语句 -
测试
-
小经验
-
直接生成
mybatis
的两类xml
文件 -
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>
<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:///web17_mybatis01"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>
<!-- 加载映射配置文件 -->
<mappers>
<mapper resource="dao/StudentDao.xml"/>
</mappers>
</configuration>
映射配置文件
<?xml version="1.0" encoding="UTF-8" ?>
<!--
xml文档第一行是文档声明,必须在首行
接下来是:命名空间。作用如下:
1. 引入dtd文件
2. 对文档中标签及其属性进行约束
-->
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- 把当前这个xml配置文件当做对应接口的实现类
通过namespace属性指向要被实现的接口,接口代理的方式必须这样写
-->
<mapper namespace=".dao.StudentDao">
<!--
标签名:select 表示查询
id属性 id唯一标识,要配置被实现的接口中的方法
resultType属性 方法的结果(集合中泛型)的类型,全限定类名,不能省略
parameterType属性,方法发参数的类型,全限定类名,可以省略
标签体中书写SQL语句
整个标签被称之为:statement
-->
<select id="findById" resultType="domain.Student">
select * from student where id = 1;
</select>
</mapper>
接口StudentDao.java
public interface StudentDao {
Student findById();
}
测试类MybatisTest.ava
public class MybatisTest {
@Test
public void test01() throws IOException {
// 配置文件位置
String resource = "mybatis-config.xml";
// 加载配置文件
InputStream inputStream = Resources.getResourceAsStream(resource);
// 通过构建的方式创建了SqlSessionFactory对象
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
// 获取session对象
// SqlSession session = sqlSessionFactory.openSession();
// JDK7 新特性 try-with-resources
/**
* try(创建各种连接对象){
* 使用连接
* }
* 不需要自己关闭连接,这个结构会在执行完代码之后自动帮你关闭
*/
// 获取session对象
try (SqlSession session = sqlSessionFactory.openSession()) {
// 通过连接对象,获取dao层实现类对象
StudentDao studentDao = session.getMapper(StudentDao.class);
// 调用dao层实现类对象中的方法
Student student = studentDao.findById();
System.out.println("student = " + student);
// 提示让删除,因为已经关闭过了
// session.close();
}
}
}
2. API
2.1 Resources
读取配置的文件的工具类,底层使用的是classLoad对象.getResourceAsStream
InputStream getResourceAsStream
(“配置文件相对于类路径的相对路径”);
2.2 SqlSessionFactoryBuilder
获取SqlSessionFactory
工厂对象的功能类。
// 通过构建的方式构建了一个SqlSessionFactory
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//xxxBuilder.build()
2.3 SqlSessionFactory
org.apache.ibatis.session.SqlSessionFactory
:获取 SqlSession
对象的工厂接口。
SqlSession openSession()
获取SqlSession
对象,并开启事务,需要手动提交事务
SqlSession openSession(boolean autoCommit)
获取SqlSession
对象,true
表示自动提交
2.4SqlSession
org.apache.ibatis.session.SqlSession
:sql会话对象接口。用于执行SQL
、管理事务、接口代理。
void | commit() | 提交事务 |
void | rollback() | 回滚事务 |
T | getMapper(Class cls) | 获取指定接口的代理实现类对象 |
void | close() | 释放资源 |
3. 映射配置文件
3.1 基本讲解
<?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">
<!-- 上面的xml的命名空间的作用
1. 引入dtd约束文件,限制文档中标签名称和属性
2. 提示功能(配合插件使用)
-->
<!--
mapper mybatis映射配置文件的根标签
属性namespace 指向对应的接口全类名
就相当于 当前这个映射配置文件可以看做是一个实现类,通过namespace指定被实现的接口
-->
<mapper namespace="dao.StudentDao">
<!-- 映射配置文件,现在可以看做是接口的实现类,要求如下:
1. 文件名和对应的接口名一致
2. 放在和接口相同的包下
3. 该配置文件的namespace的值为,接口的全限定类名
-->
<!-- 实现接口中的抽象方法,mapper下每一个字标签都可以被称之为一个statement,可以是insert/update/delete/select
select标签标示要查询
id属性要和对应的方法名一致
resultType 返回值类型,与方法的返回值 类型要一致
parameterType 参数类型,现在没有,就省略不写
标签体中编写SQL语句
-->
<select id="findById" resultType="domain.Student">
select * from student where id = 2
</select>
</mapper>
参数相关
编码完成如下需求:
// 需求1:查询id为1的学生(强制要求)
// 需求2:创建一个Student对象并保存到数据库(参数和成员变量名保持一致)(成功了吗?)
// 需求3:根据学生姓名和年龄查询用户,传两个参数(多个参数怎么办?)
// 需求4:完成需求3,传一个参数。
映射配置文件StudentDao.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 namespace="dao.StudentDao">
<!--// 需求1:查询id为1的学生(强制要求)
当接口的参数只有一个,且类型为普通类型(基本数据类型 + String)
在SQL中个,可以使用任意字符接收传递的参数 格式 #{任意字符}
可以在接口形参上添加注解@Param("标识"),在SQL语句中,只能使用该标识或者param[n]来接收
限制接收参数的名称之后,乱写造成的异常
BindingException: Parameter 'xxxx' not found. Available parameters are [idx, param1]
-->
<!-- <select id="findById" parameterType="integer" resultType="domain.Student">
select * from student where id = #{idx}
</select>-->
<!--<select id="findById" parameterType="integer" resultType="domain.Student">
select id, first_name firstName, age from student where id = #{idx}
</select>-->
<!--
// 需求2:创建一个Student对象并保存到数据库(参数和成员变量名保持一致)(成功了吗?)
// 需求3:根据学生姓名和年龄查询用户,传两个参数(多个参数怎么办?)
-->
<!-- // 需求2:创建一个Student对象并保存到数据库
当接口中参数只有一个,且为JavaBean类型时,
在SQL语句中,使用属性名直接接收即可。格式 #{属性名}
增删改 修改操作直接执行不会持久化到数据库中,因为mybatis默认开始起了事务,但是没有自动提交
解决方式1:
1. 手动提交。使用SQLSession对象的commit方法提交
2. 自动提交。获取SQLSession的时候指定自动提交。openSession(true)
关于日志:
mybatis的日志实现之一是log4j,我们需要导包 + 创建配置文件(log4j.properties)才能生效。
核心配置文件中的配置可以不配,他会自己搜索日志的实现。
-->
<!--<insert id="save" parameterType="domain.Student">-->
<insert id="save">
insert into student values(#{id},#{firstName},#{age})
</insert>
<!--
// 需求3:根据学生姓名和年龄查询用户,传两个参数(多个参数怎么办?)
List<Student> findByNameAndAge(String name, Integer age);
如果接口中参数由多个普通类型,SQL默认可以使用 arg[0+]或者 parm[1+]接收
如果想强制使用某个变量名接收,可以在接口形参位置添加@Param("标识")
错误提示信息
BindingException: Parameter 'xxx' not found. Available parameters are [arg1, arg0, param1, param2]
paramterType可以省略不写
resultType 表示的是返回值类型或者返回值集合中元素的类型
-->
<select id="findByNameAndAge" resultType="domain.Student">
select * from student where name = #{namex} and age = #{agex}
</select>
<!--
// 需求4:完成需求3,传一个参数。
List<Student> findByCondition(Student stu);
有局限性:不能按照某一个条件范围查找,eg:查找年龄介于20~25之间
因为不可能为一个成员变量赋两个值
-->
<select id="findByCondition"
resultType="domain.Student">
select * from student where name = #{name} and age = #{age}
</select>
<!--
// 需求4:完成需求3,传一个参数map。传递一个student对象
List<Student> findByConditionMap(Map map);
这个封装方式,不受上述影响,更灵活。
-->
<!--
resultType不能省略,否则报错
ExecutorException: A query was run and no Result Maps were found for the Mapped Statement 'dao.StudentDao.findByConditionMap'.
It's likely that neither a Result Type nor a Result Map was specified.
-->
<!--<select id="findByConditionMap">-->
<select id="findByConditionMap" resultType="domain.Student">
select * from student where name = #{namex} and age = #{agex}
</select>
</mapper>
核心配置文件
<!-- 加载映射配置文件 这里使用的是/ -->
<mappers>
<mapper resource="dao/StudentDao.xml"/>
</mappers>
Dao
层接口StudentDao
public interface StudentDao {
@Select("select * from student where id = 1")
Student findById();
// 需求1:查询id为1的学生(强制要求)
Student findById2(@Param("id") Integer id);
// 需求2:创建一个Student对象并保存到数据库
void save(Student stu);
// 需求3:根据学生姓名和年龄查询用户,传两个参数(多个参数怎么办?)
List<Student> findByNameAndAge(@Param("name") String name, @Param("agex") Integer age);
// 需求4:完成需求3,传一个参数。
// 4.1 把两个参数封装成一个实体对象
// 如果把多个参数封装到一个实体对象中,参数类型有局限性,不能是两个一样的条件做范围查询
List<Student> findByStudent(Student stu);
// 4.2 把两个参数封装到Map集合中
List<Student> findByMap(Map<String,Object> map);
}
测试类
/**
* @Author Vsunks.v
* @Date 2020/8/7 11:19
* @Description:
*/
public class MybatisDemo {
public static void main(String[] args) throws Exception {
// 指定核心配置文件
String resource = "mybatis-config.xml";
// 把核心配置文件加载成流 Resources为我们提供的方便读取配置文件的工具类
InputStream inputStream = Resources.getResourceAsStream(resource);
// 通过构建的方式构建了一个SqlSessionFactory
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
// SqlSession 就相当于我们之前的 Connection
// JDK7新特性 try-with-resource
// 需要释放资源的动作,自动释放在try()中开启的一些流、会话......
try (SqlSession session = sqlSessionFactory.openSession(true)) {
// getMapper getDao 获取一个指定接口的实现类对象
// 底层是动态代理
// 动态代理可以增强有接口的类、接口(增强接口,就是现实接口)
// 就相当于我们自己new StudentDaoImpl();
// 这个StudentDaoImpl是Mybatis通过动态代理帮我们自动生成并且赋值给了studentDao
StudentDao studentDao = session.getMapper(StudentDao.class);
/*List<Student> students = studentDao.findAll();
System.out.println("students = " + students);*/
// 测试根据id查询
/*Student student = studentDao.findById(1);
System.out.println("student = " + student);*/
// 测试添加学生
//studentDao.saveStudent(new Student(null,"凤姐",20));
// 根据名称和年龄查询用户,参数是两个基本类型
//List<Student> students = studentDao.findByNameAndAge("美女", 20);
//System.out.println("students = " + students);
// 根据名称和年龄查询用户,参数是一个student对象
//List<Student> students = studentDao.findByUser(new Student(null, "凤姐", 20));
//System.out.println("students = " + students);
// 根据名称和年龄查询用户,参数是一个map集合
HashMap<String, Object> map = new HashMap<>();
map.put("name", "王二蛋");
map.put("age", 20);
System.out.println("studentDao.findByMap(map) = " + studentDao.findByMap(map));
// 手动提交
//session.commit();
// session.close();
}
}
}
4. 核心配置文件
4.0 注意
核心配置文件中各种配置的顺序,需要符合官方要求,否则(运行时)会报错。错误提示如下:
<!--
如果没有按照指定顺序配置,会报错
SAXParseException; lineNumber: 66; columnNumber: 17; 元素类型为 "configuration" 的内容必须匹配 "(properties?,settings?,typeAliases?
按照错误提示,修改配置顺序即可。
-->
4.1 properties(重要)
引入外部的properties文件,一般用作引入数据库链接参数的配置。
核心配置文件
<?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">
<!-- 这是一个mybatis的核心配置文件,文件名字任意-->
<configuration>
<!--引入核心配置文件 -->
<properties resource="jdbc.properties"/>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<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="dao/StudentDao.xml"/>
</mappers>
</configuration>
jdbc.properties
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://192.168.115.130:3306/db1
# 数据库用户名可以是username之外任意名称
jdbc.username=root
jdbc.password=root
4.2 Settings
(重要)
<!-- mybatis的settings配置 -->
<settings>
<!--
1. 驼峰命名和底杠命名自动转换
mybatis可以完成查询结果集到实体对象自动封装的原理:
结果集字段名和实体属性名一致,就可自动封装
当两者不一致时,
1. 手动为结果集字段起别名,让其和实体属性名一致
2. 开启驼峰命名和底杠命名自动转换
-->
<setting name="mapUnderscoreToCamelCase" value="true"/>
<!--
2. 日志配置:
指定 MyBatis 所用日志的具体实现,未指定时将自动查找。
该值可以不用设置,mybatis会自动查找日志实现,查找顺序为:SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING。
但是对于可配置可不配置的属性,我们建议显示配置
LOG4J
要求:1. 导入log4j的jar包 2. 有一个log4j.properties文件
-->
<setting name="logImpl" value="LOG4J"/>
</settings>
4.3 TypeAliases
(了解)
<!-- 起别名 -->
<typeAliases>
<!--
type 全限定类名
alias 别名
别名不区分大小写
-->
<!--<typeAlias type="domain.Student" alias="student"/>-->
<!-- 为包下所有的实体类起别名,不区分大小写 -->
<package name="domain"/>
</typeAliases>
起别名后,在映射配置文件中类型处可以直接使用别名(不区分大小写)
<select id="findByMap" resultType="Student">
select * from student where first_username=#{name}
and
age=#{age}
</select>
系统为常见的类型起好了别名,详见官方文档
别名 | 映射的类型 |
---|---|
_byte | byte |
_long | long |
_short | short |
_int | int |
_integer | int |
_double | double |
_float | float |
_boolean | boolean |
string | String |
byte | Byte |
long | Long |
short | Short |
int | Integer |
integer | Integer |
double | Double |
float | Float |
boolean | Boolean |
date | Date |
decimal | BigDecimal |
bigdecimal | BigDecimal |
object | Object |
map | Map |
hashmap | HashMap |
list | List |
arraylist | ArrayList |
collection | Collection |
iterator | Iterator |
整体使用频率不高,因为插件会自动生成,而且写全的更好理解。
4.4 Environments
(了解)
会配置连接四要素即可
<environments default="dev">
<environment id="dev">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<!-- 连接数据库四要素 -->
<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>
<environment id="test">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<!-- 连接数据库四要素 -->
<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>
4.5 Mappers
(重点)
<!-- 配置映射配置文件(接口实现类)的位置 -->
<mappers>
<!-- 加载指定包下面的接口或者映射配置文件(优先推荐) -->
<package name="dao"/>
<!--
从类路径下加载指定的映射配置文件(次推荐)
路径中,需要使用 / 分层,而非使用 .
如果有多个映射配置文件需要加载,就需要配置多个mapper分别加载进来
-->
<!--<mapper resource="dao/StudentDao.xml"/>-->
<!--<mapper resource="dao/TeacherDao.xml"/>-->
<!-- 使用完全限定资源定位符(URL)不用 -->
<!--<mapper url="file://ip:端口/var/mappers/AuthorMapper.xml"/>-->
<!-- 加载接口,识别接口上的注解 不推荐 -->
<!--<mapper class="dao.StudentDao"/>-->
</mappers>
5. 经验分享
5.1 生成Mybatis
配置文件
本质是新建了一个文件模板,按照下面的方式新建两个模板即可(Mybatis-config
、Mybatis-Mapper
)
5.2 接口
和映射配置文件
跳转
使用一个插件free-idea-mybatis
。
这个插件是个zip包,不要解压,直接安装即可。
已知bug:
多个模块之间有相同内容会提示/跳转错误,卸载其他模块即可。
5.3 ${}和#{}区别(面试题)
#{} MyBatis 会创建 PreparedStatement
参数占位符,并通过占位符安全地设置参数(就像使用 ? 一样)。 这样做更安全,更迅速,通常也是首选做法。
${} 会做字符串的拼接,把${参数名}中参数名对应的值拼接进SQL语句,可能会有SQL注入的风险,效率更低。但是,如果你需要在SQL语句中直接插入一个不转义的字符串,可以使用这种方式。一般情况下会把表名或者字段名通过这方方式传递到SQL语句中,比方说 ORDER BY后面的列名。
5.4 MybatisUtils工具类抽取
/**
* mybatis工具类,可以实现的功能
* 1. 自动维护SQLSessionFactory
* 2. 获取SQLSession
* 3. 获取dao接口代理对象
*
* @Author Vsunks.v
* @Date 2020/12/4 16:30
* @Blog blog.sunxiaowei.net
* @Description:
*/
public class MybatisUtils {
// 私有构造
private MybatisUtils() {
}
// 维护一个SQLSessionFactory对象
private static SqlSessionFactory ssf;
// 静态代码块初始化SQLSessionFactory
static {
// 加载配置文件
String resource = "mybatis-config.xml";
try {
// 获取流
InputStream is = Resources.getResourceAsStream(resource);
// 通过builder build一个SqlSessionFactory
ssf = new SqlSessionFactoryBuilder().build(is);
} catch (IOException e) {
e.printStackTrace();
}
}
//2. 获取SQLSession
public static SqlSession getSqlSession() {
return ssf.openSession(true);
}
// 3. 获取dao接口代理对象
public static <T> T getMapper(Class<T> tClass){
return getSqlSession().getMapper(tClass);
}
}
tisUtils {
// 私有构造
private MybatisUtils() {
}
// 维护一个SQLSessionFactory对象
private static SqlSessionFactory ssf;
// 静态代码块初始化SQLSessionFactory
static {
// 加载配置文件
String resource = "mybatis-config.xml";
try {
// 获取流
InputStream is = Resources.getResourceAsStream(resource);
// 通过builder build一个SqlSessionFactory
ssf = new SqlSessionFactoryBuilder().build(is);
} catch (IOException e) {
e.printStackTrace();
}
}
//2. 获取SQLSession
public static SqlSession getSqlSession() {
return ssf.openSession(true);
}
// 3. 获取dao接口代理对象
public static <T> T getMapper(Class<T> tClass){
return getSqlSession().getMapper(tClass);
}
}