环境:
JDK1.8
MySQL 8.0.23
maven 3.6.1
IDEA2020.3
框架:需要配置文件
官方中文文档:https://mybatis.org/mybatis-3/zh/index.html
MyBatis 是一款优秀的持久层框架,是一个半自动化的 ORM 框架
不同于其他的对象关系映射框架,MyBatis 并未将 Java 对象和数据库表关联,而是将 Java 方法与 SQL 语句关联。
它支持自定义 SQL、存储过程以及高级映射。
MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。
connection
、创建statement
、手动设置参数、结果集检索等jdbc繁杂的过程代码。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。
MyBatis 的真正强大在于它的(SQL)语句映射
MyBatis本是apache的一个开源项目**iBatis
**。
2010年这个项目由apache software foundation迁移到了google code,并且改名为MyBatis。
2013年11月迁移到Github。
<dependency>
<groupId>org.mybatisgroupId>
<artifactId>mybatisartifactId>
<version>3.5.13version>
dependency>
官网:https://blog.mybatis.org/
Github:https://github.com/mybatis
什么是持久化?_51CTO博客_持久化
***持久化(Persistence) :***将程序的数据的在持久状态和瞬时状态间转化的机制。即将数据(如内存中的对象)存储在可持久保存的存储介质上(如数据库、磁盘、文件系统等)
“持久化”这个概念是和“暂时”等概念相对的,数据在计算机中有一般有两个存储地,内存为暂存,因为电源关机就会数据丢失,如果需要反复使用,就要持久保存,实现持久化了。
内存:断电即失
数据持久化 : 将内存中的数据模型转换为存储模型,以及将存储模型转换为内存中的数据模型的统称.
数据模型可以是任何数据结构或对象模型;存储模型可以是关系模型、XML、二进制流等。
cmp和Hibernate只是对象模型到关系模型之间转换的不同实现。只不过对象模型和关系模型应用广泛,所以就会误认为数据持久化就是对象模型到关系型数据库的转换罢了。
持久化的主要应用是将内存中的对象存储在关系型的数据库中,当然也可以存储在磁盘文件中、XML数据文件中等等。
JDBC就是一种持久化机制。文件IO也是一种持久化机制。
生活:冷藏、罐头
**持久化对象 :是指在程序中表示的对象,可以存储在持久化存储介质(如数据库、文件系统等)中并能够长期保留。简单来说,持久化对象是指在内存中的对象通过某种方式保存到外部存储介质中,并且在需要时可以重新加载到内存中进行使用。
***持久层(Persistence Layer):***专注于实现数据持久化应用领域的某个特定系统的一个逻辑层面,将数据使用者和数据实体相关联。(就是用于完成持久化工作的代码块(dao 层【DAO(Data Access Object)】))
保存数据对象:有一些对象,不能让它丢失
内存太昂贵
之所以需要持久化,是由于内存自身缺陷导致。我们知道,内存在遇到某些外界因素影响后会丢失,但是我们的一些数据是绝对不能丢失的,但我们又无法保证不收外界因素影响。
同时内存成本较高,比起硬盘、光盘等外存,其价格要高上几个数量级,而且维持成本也较高。在这种情况下,我们不得不寻求另一种方案来存储数据对象,而持久化就是其中的一种选择,我们能够通过持久化将数据缓存到外存,从而降低成本。
ORM-Object/Relational Mapper,即“对象-关系型数据映射组件”。对于O/R,即 Object(对象)和Relational(关系型数据),表示必须同时使用面向对象和关系型数据进行开发。
备注:建模领域中的 ORM 为Object/Role Modeling(对象角色建模)。另外这里是“O/R Mapper”而非“O/R Mapping”。相对来讲,O/R Mapping 描述的是一种设计思想或者实现机制,而 O/R Mapper指以O/R原理设计的持久化框架(Framework),包括 O/R机制还有 SQL自生成,事务处理,Cache管理等。
除了ORM 技术,还有以下持久其它持久化技术
数据持久化 - petercao - 博客园
在目前的企业应用系统设计中,MVC,即 Model(模型)- View(视图)-Control(控制)为主要的系统架构模式。MVC 中的Model 包含了复杂的业务逻辑和数据逻辑,以及数据存取机制(如 JDBC的连接、SQL生成和Statement创建、还有ResultSet结果集的读取等)等。将这些复杂的业务逻辑和数据逻辑分离,以将系统的紧耦合关系转化为松耦合关系(即解耦合),是降低系统耦合度迫切要做的,也是持久化要做的工作。MVC 模式实现了架构上将表现层(即View)和数据处理层(即Model)分离的解耦合,而持久化的设计则实现了数据处理层内部的业务逻辑和数据逻辑分离的解耦合。而 ORM 作为持久化设计中的最重要也最复杂的技术,也是目前业界热点技术。
简单来说,按通常的系统设计,使用 JDBC 操作数据库,业务处理逻辑和数据存取逻辑是混杂在一起的。
一般基本都是如下几个步骤:
1、建立数据库连接,获得 Connection 对象。
2、根据用户的输入组装查询 SQL 语句。
3、根据 SQL 语句建立 Statement 对象 或者 PreparedStatement 对象。
4、用 Connection 对象执行 SQL语句,获得结果集 ResultSet 对象。
5、然后一条一条读取结果集 ResultSet 对象中的数据。
6、根据读取到的数据,按特定的业务逻辑进行计算。
7、根据计算得到的结果再组装更新 SQL 语句。
8、再使用 Connection 对象执行更新 SQL 语句,以更新数据库中的数据。
7、最后依次关闭各个 Statement 对象和Connection 对象。
由上可看出代码逻辑非常复杂,这还不包括某条语句执行失败的处理逻辑。其中的业务处理逻辑和数据存取逻辑完全混杂在一块。而一个完整的系统要包含成千上万个这样重复的而又混杂的处理过程,假如要对其中某些业务逻辑或者一些相关联的业务流程做修改,要改动的代码量将不可想象。另一方面,假如要换数据库产品或者运行环境也可能是个不可能完成的任务。而用户的运行环境和要求却千差万别,我们不可能为每一个用户每一种运行环境设计一套一样的系统。
所以就要将一样的处理代码即业务逻辑和可能不一样的处理即数据存取逻辑分离开来,另一方面,关系型数据库中的数据基本都是以一行行的数据进行存取的,而程序运行却是一个个对象进行处理,而目前大部分数据库驱动技术(如ADO.NET、JDBC、ODBC等等)均是以行集的结果集一条条进行处理的。所以为解决这一困难,就出现ORM 这一个对象和数据之间映射技术。
针对数据库操作,Java 其实已经提供了相应的解决方案 – JDBC。那既然已经有了现成的工具,那为什么还会有 MyBatis 的出现呢?
思考:在开始之前,思考下如何通过JDBC查询Emp表中的所有记录,并封装到一个List集合中返回。(演示:准备数据、导包、导入JDBC程序)
使用传统方式JDBC访问数据库:
使用JDBC访问数据库有大量重复代码(比如注册驱动、获取连接、获取传输器、释放资源等);
JDBC自身没有连接池,会频繁的创建连接和关闭连接,效率低;
如果使用数据库连接池就能在一定程度上缓解该问题;
SQL 语句在代码中出现,会造成代码不易维护;
SQL是写死在程序中,一旦修改SQL,需要对类重新编译;
使用 preparedStatement
向占位符传递参数时存在硬编码,也会进一步加大系统维护的难度;
对结果集进行解析时存在硬编码,SQL 变化将导致解析代码改变,系统难以维护,但如果能将数据库记录进行封装成 POJO 对象,解析起来就会方便很多。
使用mybatis框架访问数据库:
Mybatis对JDBC对了封装,可以简化JDBC代码;
Mybatis自身支持连接池(也可以配置其他的连接池),因此可以提高程序的效率;
Mybatis是将SQL配置在mapper文件中,修改SQL只是修改配置文件,类不需要重新编译。
对查询SQL执行后返回的ResultSet对象,Mybatis会帮我们处理,转换成Java对象。
总之,JDBC中所有的问题(代码繁琐、有太多重复代码、需要操作太多对象、释放资源、对结果的处理太麻烦等),在Mybatis框架中几乎都得到了解决!!
简单易学:自身小且简单,无任何第三方依赖(最简单安装只要两个jar文件+配置几个sql映射文件);
灵活:MyBatis 不会对应用程序或数据库的现有设计强加任何影响,写在 XML 中,便于统一管理和优化;
解除 SQL 与代码程序的耦合:通过提供 DAO 层,将业务逻辑与数据访问逻辑分离,使系统设计更加清晰、易维护、易于单元测试。sql和代码的分离,提高了程序的可维护性;
提供 XML 标签,支持编写动态 SQL;
提供映射标签,支持对象与数据库的ORM字段关系映射。
提供对象关系映射标签,支持对象关系组建维护。
要使用 MyBatis, 只需将 mybatis-x.x.x.jar 文件置于类路径(classpath)中即可。
如果使用 Maven 来构建项目,则需将下面的依赖代码置于 pom.xml 文件中:
<dependency>
<groupId>org.mybatisgroupId>
<artifactId>mybatisartifactId>
<version>x.x.xversion>
dependency>
create database mybatis;
use mybatis;
create table user(
id int(20) not null,
name varchar(30) default null,
pwd varchar(30) default null,
primary key(id)
)ENGINE=INNODB default CHARSET=utf8;
insert into user values
(1,"狂神","123456"),
(2,"张三","892457"),
(3,"李四","784728");
select * from user;
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>8.0.23version>
dependency>
<dependency>
<groupId>org.mybatisgroupId>
<artifactId>mybatisartifactId>
<version>3.5.13version>
dependency>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>4.12version>
dependency>
创建一个module
mybatis-config.xml
中进行配置
DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"https://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.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai"/>
<property name="username" value="root"/>
<property name="password" value="niit1234"/>
dataSource>
environment>
environments>
configuration>
MySQL版本号是8.0以上,设置driver为com.mysql.cj.jdbc.Driver
;还需要设置serverTimezone
,useSSL
等参数
概念:serverTimezone连接mysql数据库时指定了时差
serverTimezone重要性
(1)时差会导致插入的date数据发生变化(自动更换时差)
(2)UTC是全球标准时间,北京地区早标准时间8小时
(3)注意使用useSSL=false
(4)&serverTimezone=Asia/Shanghai" //注意Shanghai是开头大写
`&serverTimezone=GMT%2B8`
2. 编写mybatis**工具类**——固定语句,以后可以直接使用
package com.kuang.utils;
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 java.io.IOException;
import java.io.InputStream;
public class MybatisUtils {
private static SqlSessionFactory sqlSessionFactory;
static {
try {
//读取配置文件
//下面三句话是固定的
//使用Mybatis第一步:获取sqlSessionFactory对象
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
//SqlSessionFactory build = new SqlSessionFactoryBuilder().build(inputStream);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
} catch (IOException e) {
e.printStackTrace();
}
}
//既然有了SqlSessionFactory,我们可以从中获得 SqlSession 的实例
//SqlSession 提供了在数据库执行 SQL 命令所需的所有方法。
public static SqlSession getSqlSession(){
return sqlSessionFactory.openSession();
}
}
package com.kuang.pojo;
public class User {
private int id;
private String name;
private String pwd;
public User(){
}
public User(int id, String name, String pwd) {
this.id = id;
this.name = name;
this.pwd = pwd;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPwd() {
return pwd;
}
public void setPwd(String pwd) {
this.pwd = pwd;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
", pwd='" + pwd + '\'' +
'}';
}
}
package com.kuang.dao;
import com.kuang.pojo.User;
import java.util.List;
public interface UserDao {//Dao=Mapper
public List<User> getUserList();
}
UserDaoMapper.xml
namespace很重要
在之前版本的 MyBatis 中,**命名空间(Namespaces)**的作用并不大,是可选的。 但现在,随着命名空间越发重要,你必须指定命名空间。
命名空间的作用有两个,一个是利用更长的全限定名来将不同的语句隔离开来,同时也实现了你上面见到的接口绑定。就算你觉得暂时用不到接口绑定,你也应该遵循这里的规定,以防哪天你改变了主意。 长远来看,只要将命名空间置于合适的 Java 包命名空间之中,你的代码会变得更加整洁,也有利于你更方便地使用 MyBatis。
**命名解析:**为了减少输入量,MyBatis 对所有具有名称的配置元素(包括语句,结果映射,缓存等)使用了如下的命名解析规则。
全限定名(比如 “com.mypackage.MyMapper.selectAllThings)将被直接用于查找及使用。
短名称(比如 “selectAllThings”)如果全局唯一也可以作为一个单独的引用。 如果不唯一,有两个或两个以上的相同名称(比如 “com.foo.selectAllThings” 和 “com.bar.selectAllThings”),那么使用时就会产生“短名称不唯一”的错误,这种情况下就必须使用全限定名。
DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.kuang.dao.UserDao">
<select id="getUserList" resultType="com.kuang.pojo.User">
select * from mybatis.user
select>
mapper>
mybatis-config.xml
中注册mapper,如不添加报错——org.apache.ibatis.binding.BindingException: Type interface com.kuang.dao.UserDao is not known to the MapperRegistry.
<mappers>
<mapper resource="com/kuang/dao/UserDaoMapper.xml"/>
mappers>
@Test
public void test(){
//第一步:获取SqlSession对象
SqlSession sqlSession= MybatisUtils.getSqlSession();
//执行SQL
UserDao userDao=sqlSession.getMapper(UserDao.class);
List<User> userList = userDao.getUserList();
for(User user:userList){
System.out.println(user);
}
}
Caused by: java.io.IOException: Could not find resource com/kuang/dao/UserDaoMapper.xml
即target/classes/com/kuang/dao下没有产生UserDaoMapper.xml
解决:在pom.xml中添加如下代码
src/main/resources
**/*.properties
**/*.xml
true
src/main/java
**/*.properties
**/*.xml
true
Cause: org.apache.ibatis.builder.BuilderException: Error creating document instance. Cause: com.sun.org.apache.xerces.internal.impl.io.MalformedByteSequenceException: 1 字节的 UTF-8 序列的字节 1 无效。
解决:Maven静态资源过滤问题 ,需要在pom.xml中加入resources
注意:我们修改完以后,要mvn clean
一下,再编译,否则还会报错
注意问题
配置文件没有注册
绑定接口错误
方法名不对
返回类型不对
maven导出资源问题
SqlSessionFactoryBuilder
这个类可以被实例化、使用和丢弃,一旦创建了 SqlSessionFactory,就不再需要它了。 因此 SqlSessionFactoryBuilder 实例的最佳作用域是方法作用域(也就是局部方法变量)。 你可以重用 SqlSessionFactoryBuilder 来创建多个 SqlSessionFactory 实例,但最好还是不要一直保留着它,以保证所有的 XML 解析资源可以被释放给更重要的事情。
SqlSessionFactory
SqlSessionFactory 一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建另一个实例。 使用 SqlSessionFactory 的最佳实践是在应用运行期间不要重复创建多次,多次重建 SqlSessionFactory 被视为一种代码“坏习惯”。因此 SqlSessionFactory 的最佳作用域是应用作用域。 有很多方法可以做到,最简单的就是使用单例模式或者静态单例模式。
SqlSession
每个线程都应该有它自己的 SqlSession 实例。SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域。 绝对不能将 SqlSession 实例的引用放在一个类的静态域,甚至一个类的实例变量也不行。 也绝不能将 SqlSession 实例的引用放在任何类型的托管作用域中,比如 Servlet 框架中的 HttpSession。 如果你现在正在使用一种 Web 框架,考虑将 SqlSession 放在一个和 HTTP 请求相似的作用域中。 换句话说,每次收到 HTTP 请求,就可以打开一个 SqlSession,返回一个响应后,就关闭它。 这个关闭操作很重要,为了确保每次都能执行关闭操作,你应该把这个关闭操作放到 finally 块中。 下面的示例就是一个确保 SqlSession 关闭的标准模式:
try (SqlSession session = sqlSessionFactory.openSession()) {
// 你的应用逻辑代码
}
在所有代码中都遵循这种使用模式,可以保证所有数据库资源都能被正确地关闭。
映射器 是一些绑定映射语句的接口。
映射器接口的实例是从 SqlSession 中获得的。
虽然从技术层面上来讲,任何映射器实例的最大作用域与请求它们的 SqlSession 相同。但方法作用域才是映射器实例的最合适的作用域。 也就是说,映射器实例应该在调用它们的方法中被获取,使用完毕之后即可丢弃。
映射器实例并不需要被显式地关闭。尽管在整个请求作用域保留映射器实例不会有什么问题,但是你很快会发现,在这个作用域上管理太多像 SqlSession 的资源会让你忙不过来。 因此,最好将映射器放在方法作用域内。就像下面的例子一样:
try (SqlSession session = sqlSessionFactory.openSession()) {
BlogMapper mapper = session.getMapper(BlogMapper.class);
// 你的应用逻辑代码
}
https://blog.csdn.net/Doraemon_Nobita/article/details/116044160
通过SqlSession.getMapper(XXXMapper.class)方法,MyBatis 会根据相应的接口声明的方法信息,通过动态代理机制生成一个Mapper 实例,我们使用Mapper 接口的某一个方法时,MyBatis 会根据这个方法的方法名和参数类型,确定Statement Id,底层还是通过SqlSession.select(“statementId”,parameterObject);或者SqlSession.update(“statementId”,parameterObject); 等等来实现对数据库的操作, MyBatis 引用Mapper 接口这种调用方式,纯粹是为了满足面向接口编程的需要。(其实还有一个原因是在于,面向接口的编程,使得用户在接口上可以使用注解来配置SQL语句,这样就可以脱离XML配置文件,实现“0配置”)。
namespace中的包名要和Dao/Mapper 接口的包名一致!
1个Dao接口类对应1个mapper,也对应1个namespace,
1个Dao接口中的方法对应1个namespace中一个SQL语句
id:对应的namespace接口中的方法名
resultType:SQL语句执行的返回值——承载数据的实体类
parameterType:参数类型
public interface UserMapper {
// 查询全部用户
List<User> getUserList();
// 根据id查询用户
User getUserById(int id);
// 插入一个用户
int addUser(User user);
// 修改一个用户
int updateUser(User user);
// 删除一个用户
void deleteUser(int id);
}
DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.kuang.dao.UserMapper">
<select id="getUserList" resultType="com.kuang.pojo.User">
select * from mybatis.user
select>
<select id="getUserById" parameterType="int" resultType="com.kuang.pojo.User">
select * from user where id=#{id}
select>
<insert id="addUser" parameterType="com.kuang.pojo.User">
insert into user(id,name,pwd) values(#{id},#{name},#{pwd})
insert>
<update id="updateUser" parameterType="com.kuang.pojo.User">
update user set name=#{name},pwd=#{pwd} where id=#{id}
update>
<delete id="deleteUser" parameterType="int">
delete from user where id=#{id}
delete>
mapper>
DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"https://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.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai"/>
<property name="username" value="root"/>
<property name="password" value="niit1234"/>
dataSource>
environment>
environments>
<mappers>
<mapper resource="com/kuang/mapper/UserDaoMapper.xml"/>
mappers>
configuration>
package com.kuang.dao;
import com.kuang.Utils.MybatisUtils;
import com.kuang.pojo.User;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
import java.util.List;
public class UserDaoTest {
//对于@Test的方法,光标聚焦在对应方法上右键
@Test
public void test(){
//第一步:获取SqlSession对象
SqlSession sqlSession= MybatisUtils.getSqlSession();;
try{
//方式1:getMapper()
UserMapper userMapper =sqlSession.getMapper(UserMapper.class);
List<User> userList = userMapper.getUserList();
for(User user:userList){
System.out.println(user);
}
System.out.println();
//方式2:selectList() 不推荐
// List userList2=sqlSession.selectList("com.kuang.dao.UserMapper.getUserList");
// for(User user : userList2){
// System.out.println(user);
// }
}catch(Exception e){
e.printStackTrace();
}finally{
//关闭sqlSession
sqlSession.close();
}
}
@Test
public void getUserById(){
SqlSession sqlSession=MybatisUtils.getSqlSession();
try{
UserMapper mapper=sqlSession.getMapper(UserMapper.class);
User user = mapper.getUserById(1);
System.out.println(user);
}catch(Exception e){
e.printStackTrace();
}finally {
sqlSession.close();
}
}
//增删改必须要提交事务
@Test
public void addUser(){
SqlSession sqlSession=MybatisUtils.getSqlSession();
try{
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
//修改Userdao中的该方法返回值为void则没有返回值
int res=mapper.addUser(new User(4,"赵六","123489"));
if(res>0){
System.out.println("插入成功");
}
//提交事务
sqlSession.commit();
}catch(Exception e){
e.printStackTrace();
}finally {
sqlSession.close();
}
}
@Test
public void updateUser(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper=sqlSession.getMapper(UserMapper.class);
int res=mapper.updateUser(new User(4,"赵六六","124376"));
if(res>0){
System.out.println("修改成功");
}
sqlSession.commit();
sqlSession.close();
}
@Test
public void deleteUser(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
int res=mapper.deleteUser(4);
if(res>0) {
sqlsession.commit();
System.out.println("SUCCESS!");
}else {
sqlsession.rollback();
System.out.println("FAILED!");
}
sqlSession.commit();
sqlSession.close();
}
}
sqlSession.commit()
标签不要匹配错
resource绑定mapper,需要使用路径
程序配置文件必须符合规范
空指针异常,没有注册到资源
target输出xml文件中存在中文乱码问题
maven资源没有导出问题
假设我们的实体类,或者数据库中的表,字段或者参数过多,我们应当考虑使用Map!
//万能的map
int addUser2(Map<String,Object> map);
<insert id="addUser2" parameterType="map" >
insert into mybatis.user(id,name,pwd) values (#{userid},#{userName},#{passWord});
insert>
@Test
public void addUser2(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
HashMap<String, Object> map = new HashMap<>();
map.put("userid",5);
map.put("userName","hello");
map.put("passWord","976543");
mapper.addUser2(map);
sqlSession.commit();
sqlSession.close();
}
Map 传递参数,直接在sql中取出key 【parameterType=“Map”
】
对象传递参数,直接在sql中取对象的属性即可 【parameterType=“com.kuang.pojo.User”
】
只有一个基本类型参数的情况下,可以直接在sql中取到
多个参数用Map或注解
like+通配符
通配符:%——0或多个 ;_——1个
两种方法:
<select id="getUserLike">
select * from user where name like "%#{value}%"
select>
//模糊查询
List<User> getUserLike(String value);
<select id="getUserLike">
select * from user where name like #{value}
select>
@Test
public void getUserLike(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> userlist = mapper.getUserLike("李%");
for(User user:userlist){
System.out.println(user);
}
}
Mybatis-03
MyBatis 的配置文件包含了会深深影响 MyBatis 行为的设置和属性信息。 配置文档的顶层结构如下:
configuration(配置)
properties(属性)
settings(设置)
typeAliases(类型别名)
typeHandlers(类型处理器)
objectFactory(对象工厂)
plugins(插件)
environments(环境配置)
environment(环境变量)
transactionManager(事务管理器)
dataSource(数据源)
databaseIdProvider(数据库厂商标识)
mappers(映射器)
新建一个mybatis-02模块:
1. 在src/main/resources路径下建立mybatis-config.xml文件
2. 在src/main/java/com/qjd/utils路径下编写工具类MybatisUtils.java读取配置文件获取sqlsessionfactory
3. 在src/main/java/com/qjd/pojo路径下编写实体类User.java
4. 在src/main/java/com/qjd/dao路径下编写接口UserMapper.java和UserMapper.xml
5. 编写测试类
MyBatis 可以配置成适应多种环境,根据id识别不同环境
尽管可以配置多个环境,但每个 SqlSessionFactory 实例只能选择一种环境。
学会配置多套运行环境-----更改id
Mybatis默认的事务管理器就是JDBC
,连接池:POOLED
连接池:使并发 Web 应用快速响应请求
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai"/>
<property name="username" value="root"/>
<property name="password" value="niit1234"/>
dataSource>
environment>
environments>
事务管理器
有三种内建的数据源类型
unpooled: 这个数据源的实现只是每次被请求时打开和关闭连接。
pooled: 这种数据源的实现利用“池”的概念将 JDBC 连接对象组织起来 , 这是一种使得
并发 Web 应用快速响应请求的流行处理方式。
jndi:这个数据源的实现是为了能在如 Spring 或应用服务器这类容器中使用,容器可以集中或在外部配置数据源,然后放置一个 JNDI 上下文的引用。
数据源也有很多第三方的实现,比如dbcp,c3p0,druid等等…
数据库这些属性都是可外部配置且可动态替换的
我们可以通过properties属性来实现引用属性文件
这些属性可以在外部进行配置,并可以进行动态替换。你既可以在典型的 Java 属性文件【db.properties】中配置这些属性,也可以在 properties 元素的子元素中设置
我们来优化我们的配置文件
db.properties
:driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
username=root
password=niit1234
db.properties
文件导入核心 配置文件mybatis-config.xml
<properties resource="db.properties">
<property name="username" value="root"/>
<property name="password" value="niit12"/>
properties>
也可以在 SqlSessionFactoryBuilder.build() 方法中传入属性值。例如:
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader, props);
// ... 或者 ...
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader, environment, props);
注意事项:
可以直接引入外部文件
可以在
中通过
增加一些属性配置
如果两个文件有同一个字段,优先使用外部配置文件(db.proerpties)的
如果一个属性在不只一个地方进行了配置,那么,MyBatis 将按照下面的顺序来加载:
首先读取在 properties 元素体内指定的属性。
然后根据 properties 元素中的 resource 属性读取类路径下属性文件(如com/kuang/dao/UserDaoMapper.xml),或根据url 属性指定的路径读取属性文件,并覆盖之前读取过的同名属性。
最后读取作为方法参数传递的属性,并覆盖之前读取过的同名属性。
通过方法参数传递的属性具有最高优先级,resource/url 属性中指定的配置文件次之,最低优先级的则是 properties 元素中指定的属性。
类型别名可为 Java 类型设置一个缩写名字。
它仅用于 XML 配置,意在降低冗余的全限定类名书写。
<typeAliases>
<typeAlias type="com.kuang.pojo.User" alias="User"/>
typeAliases>
也可以指定一个包名,MyBatis 会在包名下面搜索需要的 Java Bean,比如:
即扫描实体类所在的包,包下类的类名就是该类的别名(首字母小写,大写也行)
<typeAliases>
<typeAlias type="com.kuang.pojo.User" alias="User"/>
<package name="com.kuang.pojo"/>
typeAliases>
每一个在包 com.kuang.pojo.User
中的 Java Bean,在没有注解的情况下,会使用 Bean 的首字母小写的非限定类名来作为它的别名。 比如 com.kuang.pojo.Author
的别名为 author
;若有注解,则别名为其注解值。见下面的例子:
@Alias("author")
public class Author {
...
}
实体类比较少的时候,使用第一种方式
如果实体类十分多,建议使用第二种
第一种可以DIY别名,第二种则不行,如果非要改,需要在实体类上增加注解
@Alias("Hello")
public class User
<select id="getUserList" resultType="Hello">
select * from mybatis.user
select>
下面是一些为常见的 Java 类型内建的类型别名。它们都是不区分大小写的,注意,为了应对原始类型的命名重复,采取了特殊的命名风格。(八大基本数据类型前加下划线,对应的包装类小写即可)
别名 | 映射的类型 |
---|---|
_byte | byte |
_char (since 3.5.10) | char |
_character (since 3.5.10) | char |
_long | long |
_short | short |
_int | int |
_integer | int |
_double | double |
_float | float |
_boolean | boolean |
string | String |
byte | Byte |
char (since 3.5.10) | Character |
character (since 3.5.10) | Character |
long | Long |
short | Short |
int | Integer |
integer | Integer |
double | Double |
float | Float |
boolean | Boolean |
date | Date |
decimal | BigDecimal |
bigdecimal | BigDecimal |
biginteger | BigInteger |
object | Object |
date[] | Date[] |
decimal[] | BigDecimal[] |
bigdecimal[] | BigDecimal[] |
biginteger[] | BigInteger[] |
object[] | Object[] |
map | Map |
hashmap | HashMap |
list | List |
arraylist | ArrayList |
collection | Collection |
iterator | Iterator |
这是 MyBatis 中极为重要的调整设置,它们会改变 MyBatis 的运行时行为。 下表描述了设置中各项设置的含义、默认值等。(部分重要设置,官网有完整版)
设置名 | 描述 | 有效值 | 默认值 |
---|---|---|---|
cacheEnabled | 全局性地开启或关闭所有映射器配置文件中已配置的任何缓存。 | true | false |
lazyLoadingEnabled | 延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。 特定关联关系中可通过设置 fetchType 属性来覆盖该项的开关状态。 |
true | false |
mapUnderscoreToCamelCase | 是否开启驼峰命名自动映射,即从经典数据库列名 A_COLUMN 映射到经典 Java 属性名 aColumn。 | true | false |
logImpl | 指定 MyBatis 所用日志的具体实现,未指定时将自动查找。 | SLF4J | LOG4J(3.5.9 起废弃) |
一个配置完整的 settings 元素的示例如下:
<settings>
<setting name="cacheEnabled" value="true"/>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="true"/>
<setting name="multipleResultSetsEnabled" value="true"/>
<setting name="useColumnLabel" value="true"/>
<setting name="useGeneratedKeys" value="false"/>
<setting name="autoMappingBehavior" value="PARTIAL"/>
<setting name="autoMappingUnknownColumnBehavior" value="WARNING"/>
<setting name="defaultExecutorType" value="SIMPLE"/>
<setting name="defaultStatementTimeout" value="25"/>
<setting name="defaultFetchSize" value="100"/>
<setting name="safeRowBoundsEnabled" value="false"/>
<setting name="safeResultHandlerEnabled" value="true"/>
<setting name="mapUnderscoreToCamelCase" value="false"/>
<setting name="localCacheScope" value="SESSION"/>
<setting name="jdbcTypeForNull" value="OTHER"/>
<setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/>
<setting name="defaultScriptingLanguage" value="org.apache.ibatis.scripting.xmltags.XMLLanguageDriver"/>
<setting name="defaultEnumTypeHandler" value="org.apache.ibatis.type.EnumTypeHandler"/>
<setting name="callSettersOnNulls" value="false"/>
<setting name="returnInstanceForEmptyRow" value="false"/>
<setting name="logPrefix" value="exampleLogPreFix_"/>
<setting name="logImpl" value="SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING"/>
<setting name="proxyFactory" value="CGLIB | JAVASSIST"/>
<setting name="vfsImpl" value="org.mybatis.example.YourselfVfsImpl"/>
<setting name="useActualParamName" value="true"/>
<setting name="configurationFactory" value="org.mybatis.example.ConfigurationFactory"/>
settings>
typeHandlers(类型处理器)
objectFactory(对象工厂)
plugins(插件)
mybatis-generator-core
mybatis-plus
通用mapper
MapperRegistry:注册绑定我们的Mapper文件,每写一个dao接口就要写一个Mapper文件
映射器 : 定义映射SQL语句文件
既然 MyBatis 的行为其他元素已经配置完了,我们现在就要定义 SQL 映射语句了。但是首先我们需要告诉 MyBatis 到哪里去找到这些语句。Java 在自动查找这方面没有提供一个很好的方法,所以最佳的方式是告诉 MyBatis 到哪里去找映射文件。你可以使用相对于类路径的资源引用, 或完全限定资源定位符(包括 file:///
的 URL),或类名和包名等。映射器是MyBatis中最核心的组件之一,在MyBatis 3之前,只支持xml映射器,即:所有的SQL语句都必须在xml文件中配置。而从MyBatis 3开始,还支持接口映射器,这种映射器方式允许以Java代码的方式注解定义SQL语句,非常简洁。
1张表——1个Entity类——1个EntityDao/EntityMapper接口——1个Mapper.xml——核心配置文件中的1个
Dao/Maapper接口中的1个方法——Mapper.xml中的1个sql语句
resource属性
使用相对于类路径的资源引用(推荐)
<mappers>
<mapper resource="com/kuang/dao/UserDaoMapper.xml"/>
mappers>
class属性
使用映射器接口实现类的完全限定类名
需要配置文件名称和接口名称一致,并且位于同一目录下
<mappers>
<mapper class="com.kuang.dao.UserMapper"/>
mappers>
注意点:
- 接口和它的Mapper配置文件必须同名
- 接口和它的Mapper配置文件必须在同一个包下
元素
将包内的映射器接口实现全部注册为映射器,但是需要配置文件名称和接口名称一致,并且位于同一目录下
<mappers>
<package name="com.kuang.dao"/>
mappers>
注意点同方法2(同名同包)
接口和它的Mapper配置文件必须同名
接口和它的Mapper配置文件必须在同一个包下
url属性
(不常用)
<mappers>
<mapper url="file:///var/mappers/AuthorMapper.xml"/>
<mapper url="file:///var/mappers/BlogMapper.xml"/>
<mapper url="file:///var/mappers/PostMapper.xml"/>
mappers>
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-l9lr11MK-1680880094413)(null)]
不同作用域和生命周期类别是至关重要的,因为错误的使用会导致非常严重的并发问题。
SqlSessionFactoryBuilder
作用:创建 SqlSessionFactory
一旦创建了 SqlSessionFactory,就不再需要它了。
SqlSessionFactoryBuilder 实例的最佳作用域是方法作用域(也就是局部方法变量)。
SqlSessionFactory
可以理解为数据库连接池
SqlSessionFactory 一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建另一个实例。
一般的应用中我们往往希望 SqlSessionFactory 作为一个单例,让它在应用中被共享。
SqlSessionFactory 的最佳作用域是应用作用域(Application Scope)。
最简单的就是使用单例模式或者静态单例模式
SqlSession
(相当于从连接池中获取一个连接)
连接到连接池的一个请求,相当于一个数据库连接(Connnection对象)
SqlSession 的实例不是线程安全的,因此是不能被共享的,它的最佳的作用域是请求或方法作用域
用完之后需要赶紧关闭,否则资源被占用。用 try…catch…finally… 语句来保证其正确关闭。
可以在一个事务里面执行多条 SQL,然后通过它的 commit、rollback 等方法,提交或者回滚事务。
一个 SqlSession 可以多次使用它的 getMapper 方法,获取多个 mapper 接口实例
打个比方:
数据库中的字段
新建一个项目Mybatis-04,拷贝之前,测试实体类字段不一致的情况
public class User {
private int id;
private String name;
private String password;
}
测试结果:password字段为空
问题原因:实体类的属性名和数据库的字段名不同
select * from mybatis.user where id =#{id}
-- 等效于 类型映射器
select id,name,pwd from mybatis.user where id =#{id}
//此时已经没有pwd
解决方法:起别名
<select id="getUserById" parameterType="int" resultType="com.qjd.pojo.User">
select id,name,pwd as password from mybatis.user where id =#{id}
select>
在UserMapper.xml的元素中添加
<resultMap id="UserMap" type="com.kuang.pojo.User">
<result column="pwd" property="password"/>
resultMap>
<select id="getUserById" parameterType="int" resultMap="UserMap">
select * from user where id=#{id}
select>
resultMap
元素是 MyBatis 中最重要最强大的元素
ResultMap 的设计思想是,对简单的语句做到零配置,对于复杂一点的语句,只需要描述语句之间的关系就行了
MyBatis 会在幕后自动创建一个 ResultMap,再根据属性名来映射列到 JavaBean 的属性上。
在之前的学习中resultMap都是被隐式创建的,因此需保证实体类的属性名和数据库的列名或列别名对应相同
中的type为全限定名或类型别名
select、update、insert、delete中的属性可以使用 resultType 或 resultMap,但不能同时使用。
回顾步骤
新建一个mybatis-04模块:
在src/main/resources路径下建立mybatis-config.xml文件建立核心配置文件
在src/main/java/com/qjd/utils路径下编写工具类MybatisUtils.java读取配置文件获取sqlsessionfactory
在src/main/java/com/qjd/pojo路径下编写实体类User.java
在src/main/java/com/qjd/dao路径下编写接口UserMapper.java和UserMapper.xml
编写测试类
(核心配置文件的
中配置)如果一个数据库操作出现了异常,我们需要排错。日志就是最好的助手!
曾经:debug、sout
现在:日志工厂
value属性只能为以下值:
SLF4J
LOG4J 【掌握】
LOG4J2
JDK_LOGGING
COMMONS_LOGGING
STDOUT_LOGGING【掌握】
NO_LOGGING
STDOUT_LOGGING
——标准的日志工厂实现在核心配置文件中配置日志实现
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
settings>
Log4j
什么是LOG4J
Log4j是Apache的一个开源项目
通过使用Log4j,我们可以控制日志信息输送的目的地是控制台、文件、GUI组件
我们可以控制每一条日志的输出格式;
通过定义每一条日志信息的级别,我们能够更加细致地控制日志的生成过程。
通过一个配置文件来灵活地进行配置,而不需要修改应用的代码。
<dependency>
<groupId>log4jgroupId>
<artifactId>log4jartifactId>
<version>1.2.17version>
dependency>
### 配置根 ###
log4j.rootLogger = debug,console,file
### 配置输出到控制台 ###
log4j.appender.console = org.apache.log4j.ConsoleAppender
log4j.appender.console.Target = System.out
log4j.appender.console.Threshold = debug
log4j.appender.console.layout = org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern = %d{ABSOLUTE} %5p %c{1}:%L - %m%n
### 配置输出到文件 ###
log4j.appender.file = org.apache.log4j.FileAppender
log4j.appender.file.File = ./log/qjd.log
log4j.appender.file.Append = true
log4j.appender.file.Threshold = debug
log4j.appender.file.layout = org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern = %-d{yyyy-MM-dd HH:mm:ss} [ %t:%r ] - [ %p ] %m%n
### 配置输出到文件,并且每天都创建一个文件 ###
log4j.appender.dailyRollingFile = org.apache.log4j.DailyRollingFileAppender
log4j.appender.dailyRollingFile.File = logs/log.log
log4j.appender.dailyRollingFile.Append = true
log4j.appender.dailyRollingFile.Threshold = debug
log4j.appender.dailyRollingFile.layout = org.apache.log4j.PatternLayout
log4j.appender.dailyRollingFile.layout.ConversionPattern = %-d{yyyy-MM-dd HH:mm:ss} [ %t:%r ] - [ %p ] %m%n
### 设置输出sql的级别,其中logger后面的内容全部为jar包中所包含的包名 ###
log4j.logger.org.mybatis=debug
log4j.logger.java.sql=debug
log4j.logger.java.sql.Connection=debug
log4j.logger.java.sql.Statement=debug
log4j.logger.java.sql.PreparedStatement=debug
log4j.logger.java.sql.ResultSet=debug
<settings>
<setting name="logImpl" value="LOG4J"/>
settings>
import org.apache.log4j.Logger;
static Logger logger = Logger.getLogger(UserDaoTest.class);
@Test
public void testLog4j(){
//相当与sout,但输出的日志级别不同
logger.info("info:进入testLog4j");
logger.debug("debug:进入了testLog4j");
logger.error("error:进入了testLog4j");
}
控制台输出:(日志文件中也会添加以下输出)
21:07:25,697 INFO UserDaoTest:63 - info:进入testLog4j
21:07:25,702 DEBUG UserDaoTest:64 - debug:进入了testLog4j
21:07:25,702 ERROR UserDaoTest:65 - error:进入了testLog4j
为什么要分页?减少数据的处理量
SELECT * FROM user limit startIndex,pageSize;
SELECT * FROM user limit 0,2;--从第1行开始(包括第1行)显示2条记录,即(1,2]或[2,2]
SELECT * FROM user limit 3; --[0,n]显示前3条记录
/**
* 分页查询用户
* @param map
* @return
*/
List<User> getUserByLimit(Map<String,Object> map);
<select id="getUserByLimit" resultMap="UserMap" parameterType="map">
select * from users limit #{startIndex},#{pageSize}
select>
@Test
public void getUserByLimit(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
HashMap<String,Integer> hashMap=new HashMap<>();
hashMap.put("startIndex",1);
hashMap.put("pageSize",2);
List<User> userList = mapper.getUserByLimit(hashMap);
for (User user : userList) {
System.out.println(user);
}
sqlSession.close();
}
输出:
User{id=2, name='张三', password='892457'}
User{id=3, name='李四', password='784728'}
//RowBounds分页
List getUserByRowBounds();
@Test
public void getUserByRowRounds(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
//RowBounds实现分页
RowBounds rowBounds = new RowBounds(1, 2);
List userList=sqlSession.selectList("com.kuang.dao.UserMapper.getUserByRowBounds",null,rowBounds);
for (User user : userList) {
System.out.println(user);
}
sqlSession.close();
}
输出:
User{id=2, name='张三', password='892457'}
User{id=3, name='李四', password='784728'}
MyBatis 分页插件 PageHelper
如何使用----如何使用分页插件
我们之前都学过面向对象编程,也学习过接口,但在真正的开发中,很多时候我们会选择面向接口编程
根本原因 : 解耦 , 可拓展 , 提高复用 , 分层开发中 , 上层不用管具体的实现 , 大家都遵守共同的标准 , 使得开发变得容易 , 规范性更好
在一个面向对象的系统中,系统的各种功能是由许许多多的不同对象协作完成的。在这种情况下,各个对象内部是如何实现自己的,对系统设计人员来讲就不那么重要了;
而各个对象之间的协作关系则成为系统设计的关键。小到不同类之间的通信,大到各模块之间的交互,在系统设计之初都是要着重考虑的,这也是系统设计的主要工作内容。面向接口编程就是指按照这种思想来编程。
关于接口的理解
接口从更深层次的理解,应是定义(规范,约束)与实现(名实分离的原则)的分离。
接口的本身反映了系统设计人员对系统的抽象理解。
接口应有两类:
第一类是对一个个体的抽象,它可对应为一个抽象体(abstract class);
第二类是对一个个体某一方面的抽象,即形成一个抽象面(interface);
一个体有可能有多个抽象面。抽象体与抽象面是有区别的。
三个面向区别
面向对象是指,我们考虑问题时,以对象为单位,考虑它的属性及方法 .
面向过程是指,我们考虑问题时,以一个具体的流程(事务过程)为单位,考虑它的实现 .
接口设计与非接口设计是针对复用技术而言的,与面向对象(过程)不是一个问题.更多的体现就是对系统整体的架构
@Select("select * from user")
List<User> getUsers();
<mappers>
<mapper class="com.kuang.dao.UserMapper"/>
mappers>
@Test
public void getUsers(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> userList = mapper.getUsers();
for (User user : userList) {
System.out.println(user);
}
sqlSession.close();
}
输出:
Opening JDBC Connection
Created connection 2125238280.
Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@7eac9008]
==> Preparing: select * from user
==> Parameters:
<== Columns: id, name, pwd
<== Row: 1, 狂神, 123456
<== Row: 2, 张三, 892457
<== Row: 3, 李四, 784728
<== Row: 5, 李六, 982374
<== Total: 4
User{id=1, name='狂神', password='null'}
User{id=2, name='张三', password='null'}
User{id=3, name='李四', password='null'}
User{id=5, name='李六', password='null'}
Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@7eac9008]
Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@7eac9008]
Returned connection 2125238280 to pool.
原因:数据库中的pwd字段和User类中的属性不同名,将User类中的password属性名改为pwd后输出正常
本质:反射机制实现
底层:动态代理
public static SqlSession getSqlSession() {
return sqlSessionFactory.openSession(true); //事务自动提交
}
public static SqlSession getSession(boolean flag){
return sqlSessionFactory.openSession(flag);
}
package com.kuang.dao;
import com.kuang.pojo.User;
import org.apache.ibatis.annotations.*;
import java.util.List;
import java.util.Map;
public interface UserMapper {
@Select("select * from user")
List<User> getUsers();
//方法存在多个参数,所有的参数前面都要加上@Param("id")注
@Select("select * from user where id=#{id}")
User getUserByID(@Param("id")int id);
@Update("update user set name=#{name},pwd=#{password} where id=#{id}")
int updateUser(User user);
@Insert("insert into user(id,name,pwd) values(#{id},#{name},#{password})")
int addUser(User user);
@Delete("delete from user where id=#{id}")
int deleteUser(@Param("uid")int id);
}
@Param
注解MyBatis(十五):@Param()注解 - 谁知道水烫不烫 - 博客园
数据库中user表:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-76nA7u9u-1686379802821)(null)]
@param
时 @Delete("delete from user where id=#{id}")
int deleteUser(@Param("uid")int id);
测试时报错:`org.apache.ibatis.exceptions.PersistenceException:
报错了,它说“id”没有找到,可用参数为“uid”
@Delete("delete from user where id=#{uid}")
int deleteUser(@Param("uid")int id);
单个基本类型的形参时不使用@Param也能执行成功。
那么其他更加复杂的情况呢,比如说,多个基本类型?引用类型?
下面我们就来深入探讨一下@Param()究竟该怎么用。
@Select("select * from user where id=#{id} and name=#{username}")
User getUserByID2(int id, String username);
@Test
public void getUserById2(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user = mapper.getUserByID2(2, "张三");
System.out.println(user);
sqlSession.close();
}
报错:org.apache.ibatis.exceptions.PersistenceException: ###Error querying database.Cause: org.apache.ibatis.binding.BindingException: Parameter 'id' not found. Available parameters are [arg1, arg0, param1, param2]
添加@Param后:
@Select("select * from user where id=#{id} and name=#{username}")
User getUserByID2(@Param("id")int id, @Param("username")String username);
测试正常运行。
以上都是举得直接在接口中注解SQL语句的例子,在Mapper.xml文件中@Param()同样使用,只需要注意的是,使用@Param()注解,parameterType属性就不用再设置了。
三、总结
简单总结一下:
sql的#{}中的参数就是@Param(“”)中设置的参数,而不是方法的形参
传入单个基本类型或String类型的时候,使用不使用@Param()注解都可以。
如果参数是 JavaBean , 则不能使用@Param。
需要传入多个参数时,有三种方法:JavaBean、@Param()注解、Map。个人认为优先使用JavaBean,当需要传入的数据较多但又不能完全将JavaBean利用起来的时候使用@Param()注解或Map。
单个基本类型,SQL对应的就是形参的名字;使用JavaBean,SQL中对应的就是JavaBean在结果集映射的属性(没有显性映射就是默认的属性);使用@Param(),SQL中对应的就是@Param()注解中的名字;使用Map,SQL中对应的就是Map的键名。
使用@Param()注解的时候,Mapper.xml文件无需再设置parameterType属性。
#{}
和${}
区别{}中的参数即为Mapper接口中对应方法的形参。
#{}
1. #{} 的作用主要是替换**预编译**语句(PrepareStatement)中的占位符? 【推荐使用】
2. 底层是preparedStatement
3. 预编译
4. 能防止sql注入,自动添加了‘ ’ 引号
例如:select * from user where username = #{name} //输入的参数lisa,就会自动加上引号,变成:select * from user where username = ‘lisa’
${}
1. 不防注入,就是**字符串拼接**
2. 只能${}的情况:order by、like 语句只能用${}了,用#{}会多个' '导致sql语句失效;此外动态拼接sql也要用${}
3. #{} 这种取值是编译好SQL语句再取值
${} 这种是取值以后再去编译SQL语句
详细讲解:
1. #将传入的数据都当成一个字符串,会对自动传入的数据加一个双引号。如:
order by #user_id#,如果传入的值是111,那么解析成sql时的值为order by "111",
如果传入的值是id,则解析成的sql为order by "id".
2. $将传入的数据直接显示生成在sql中。如:order by $user_id$,
如果传入的值是111,那么解析成sql时的值为order by user_id,
如果传入的值是id,则解析成的sql为order by id.
3. #方式能够很大程度防止sql注入。
4.$方式无法防止Sql注入。
5.$方式一般用于传入数据库对象,例如传入表名.
6.一般能用#的就别用$.
MyBatis排序时使用order by 动态参数时需要注意,用$而不是#
字符串替换
默认情况下,使用#{}格式的语法会导致MyBatis创建预处理语句属性并以它为背景设置
安全的值(比如?)。这样做很安全,很迅速也是首选做法
有时你只是想直接在sql语句中插入一个不改变的
字符串。比如,像ORDER BY,你可以这样来使用:ORDER BY ${columnName}
这里MyBatis不会修改或转义字符串。重要:接受从用户输出的内容并提供给语句中
不变的字符串,这样做是不安全的。这会导致潜在的sql注入攻击,因此你不应该
允许用户输入这些字段,或者通常自行转义并检查。
使用注解和配置文件协同开发,才是MyBatis的最佳实践!
Lombok项目是一个java库,它可以自动插入到编辑器和构建工具中,增强java的性能。不需要再写getter、setter或equals方法,只要有一个注解,你的类就有一个功能齐全的构建器、自动记录变量等等。
是一个在Java开发过程中用注解的方式,简化了 JavaBean(实体类) 的编写,避免了冗余和样板式代码而出现的插件,让编写的类更加简洁。
在IDEA中安装lombok插件:File —> Settings —> Plugins —> Browse repositories —> 搜索lombok
在pom.xml中导入lombok的maven依赖(包)
org.projectlombok
lombok
1.18.26
provided
@Data
: 自动生成set/get方法,toString方法,equals方法,hashCode方法,不带参数的构造方法
@NoArgsConstructor
/@RequiredArgsConstructor
/@AllArgsConstructor
自动生成无参/有参构造方法
@Setter
/@Getter
: 自动生成set和get方法
@ToString
: 自动生成toString方法
@EqualsAndHashcode
: 从对象的字段中生成hashCode和equals方法
以下为不常用注解:
@NonNull : 用在成员方法或者构造方法的参数前面,会自动产生一个关于此参数的非空检查,如果参数为空,则抛出一个空指针异常
@CleanUp : 自动资源管理:不用再在finally中添加资源的close方法
@Value : 用于注解final类
@Builder : 产生复杂的构建器api类
@SneakyThrows : 异常处理(谨慎使用)
@Synchronized : 同步方法安全的转化
@Getter(lazy=true) :
@Log: 支持各种logger对象,使用时用对应的注解,如:@Log4j
优点:
能通过注解的形式自动生成构造器、getter/setter、equals、hashcode、 toString等方法,提高了一定的开发效率
让代码变得简洁,不用过多的去关注相应的方法
属性做修改时,也简化了维护为这些属性所生成的getter/setter方法等
缺点:
不支持多种参数构造器的重载
虽然省去了手动创建getter/seter方法的麻烦,但大大降低了源代码的可读性和完整性,降低了阅读源代码的舒适度
Lombok虽然有很多优点,但Lombok更类似于一种IDE插件,项目也需要依赖相应的jar包。Lombok依赖jar包是因为编译时要用它的注解,为什么说它又类似插件?因为在使用时,eclipse或Intelli] DEA都需要安装相应的插件,在编译器编译时通过操作AST(抽象语法树)改变字节码生成,变向的就是说它在改变java语法。
它不像spring的依赖注入或者mybatis的ORM一样是运行时的特性,而是编译时的特性,这里我个人最感觉不爽的地方就是对插件的依赖!因为Lombok只是省去了一些人丁生成代码的麻烦,但IDE都有快捷键来协助生成etter/setter等方法,也非常方便
知乎上有位大神发表过对Lombok的一些看法:这是一种低级趣味的插件,不建议使用。JAva发展到今天,各种插件层出不穷,如何甄别各种插件的优劣?能从架构上优化你的设计的,能提高应用程序性能的,实现高度封装可扩展的..., 像1mbok这种,像这种插件,已经不仅仅是插件了,改变了你如何编写源码,事实上,少去了的代码你写上去又如何?如果JAVA家族到外充斥这样的东西,那只不过是一垃披着金属颜色的屎,迟早会被其它的语言取代。
虽然话糙但理确实不糙,试想一个项目有非常多类似Lombok这样的插件,个人觉得真的会极大的降低阅读源代码的舒适度,虽然非常不建议在属性的getter/seter写一些业务代码,但在多年项目的实战中,有时通过给getter/setter加一点点业务代码,能极大的简化某些业务场景的代码。所谓取舍,也许就是这时的舍弃一定的规范,取得极大的方便。
我现在非常坚信一条理念,任何编程语言或插件,都仅仅只是工具而已,即使工具再强大也在于用的人,就如同小米加步枪照样能前飞机大炮的道理一样。结合具体业务场景和项目实际情况,无需一味追求高大上的技术,适合的才是王道。
Lombok有它的得天独厚的优点,也有它避之不及的缺点,熟知其优缺点,在实战中灵活运用才是王道
多对一的理解:
多个学生对应一个老师
如果对于学生这边,就是一个多对一的现象,即从学生这边关联一个老师!
结果映射(resultMap):
association
collection
一个复杂类型的集合
嵌套结果映射 —— 集合可以是 resultMap 元素,或是对其它结果映射的引用
以下使用两种方式实现以下sql语句:
select s.id ,s.name ,t.name from student s,teacher t where s.tid=t.id
类似子查询
CREATE TABLE `teacher` (
`id` INT(10) NOT NULL,
`name` VARCHAR(30) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8;
INSERT INTO teacher(`id`, `name`) VALUES (1, '秦老师');
CREATE TABLE `student` (
`id` INT(10) NOT NULL,
`name` VARCHAR(30) DEFAULT NULL,
`tid` INT(10) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `fktid` (`tid`),
CONSTRAINT `fktid` FOREIGN KEY (`tid`) REFERENCES `teacher` (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8;
INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('1', '小明', '1');
INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('2', '小红', '1');
INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('3', '小张', '1');
INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('4', '小李', '1');
INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('5', '小王', '1');
public class Teacher {
private int id;
private String name;
public Teacher(){
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Teacher{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
}
public class Student {
private int id;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
private String name;
private Teacher teacher;//学生需要关联一个老师
public Student(){
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public Teacher getTeacher() {
return teacher;
}
public void setTeacher(Teacher teacher) {
this.teacher = teacher;
}
@Override
public String toString() {
return "Student{" +
"id=" + id +
", name='" + name + '\'' +
", teacher=" + teacher +
'}';
}
}
package com.kuang.dao;
import com.kuang.pojo.Student;
import java.util.List;
public interface StudentMapper {
//查询所有的学生信息,以及对应的老师的信息
public List<Student> getStudent();
}
public interface TeacherMapper {
}
@Test
public void testStudent(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
List studentList = mapper.getStudent();//动态代理产生一个实现Mapper接口的对象并赋值个,将该对象赋值给接口的引用
for (Student student : studentList) {
System.out.println(student);
}
sqlSession.close();
}
public List getStudent2();
@Test
public void testStudent(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
List studentList = mapper.getStudent2();//动态代理产生一个实现Mapper接口的对象并赋值个,将该对象赋值给接口的引用
for (Student student : studentList) {
System.out.println(student);
}
sqlSession.close();
}
teacher的id和数据库中不一样????数据库中id=1
小结
按照查询进行嵌套处理就像SQL中的子查询
按照结果进行嵌套处理就像SQL中的联表查询
package com.kuang.pojo;
public class Student {
private int id;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
private String name;
public int getTid() {
return tid;
}
public void setTid(int tid) {
this.tid = tid;
}
private int tid;
public Student(){
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
@Override
public String toString() {
return "Student{" +
"id=" + id +
", name='" + name + '\'' +
", tid=" + tid +
'}';
}
}
package com.kuang.pojo;
import java.util.List;
public class Teacher {
private int id;
private String name;
private List<Student> students;//一个老师拥有多个学生
public List<Student> getStudents() {
return students;
}
public void setStudents(List<Student> students) {
this.students = students;
}
public Teacher(){
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Teacher{" +
"id=" + id +
", name='" + name + '\'' +
", students=" + students +
'}';
}
}
List<Teacher> getTeacher();
<select id="getTeacher" resultType="Teacher">
select * from teacher;
select>
核心配置文件中注册Mapper
测试环境正常
@Test
public void test(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
TeacherMapper mapper = sqlSession.getMapper(TeacherMapper.class);
List<Teacher> teacherList = mapper.getTeacher();
for (Teacher teacher : teacherList) {
System.out.println(teacher);
}
sqlSession.close();
}
Teacher getTeacherById(@Param("tid")int id);
<select id="getTeacherById" resultMap="TeacherStudent">
select t.name tname,t.id tid,s.id sid,s.name sname
from student s,teacher t
where s.tid=t.id and t.id=#{tid}
select>
<resultMap id="TeacherStudent" type="Teacher">
<result property="id" column="tid"/>
<result property="name" column="tname"/>
<collection property="students" ofType="Student">
<result property="id" column="sid"/>
<result property="name" column="sname"/>
<result property="tid" column="tid"/>
collection>
resultMap>
@Test
public void getTeacherById(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
TeacherMapper mapper = sqlSession.getMapper(TeacherMapper.class);
Teacher teacher = mapper.getTeacherById(1);
System.out.println(teacher);
sqlSession.close();
}
输出:
Opening JDBC Connection
Created connection 1278254413.
==> Preparing: select s.id sid,s.name sname,t.name tname,t.id tid from student s,teacher t where s.tid=t.id and t.id=?
==> Parameters: 1(Integer)
<== Columns: sid, sname, tname, tid
<== Row: 1, 小明, 秦老师, 1
<== Row: 2, 小红, 秦老师, 1
<== Row: 3, 小张, 秦老师, 1
<== Row: 4, 小李, 秦老师, 1
<== Row: 5, 小王, 秦老师, 1
<== Total: 5
Teacher{id=1, name='秦老师', students=[Student{id=1, name='小明', tid=1}, Student{id=2, name='小红', tid=1}, Student{id=3, name='小张', tid=1}, Student{id=4, name='小李', tid=1}, Student{id=5, name='小王', tid=1}]}
Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@4c309d4d]
Returned connection 1278254413 to pool.
Teacher getTeacherById2(@Param("tid")int id);
<select id="getTeacherById2" resultMap="TeacherStudent2">
select * from mybatis.teacher where id=#{tid}
select>
<resultMap id="TeacherStudent2" type="Teacher">
<collection property="students" javaType="ArrayList" ofType="Student" select="getStudentByTeacherId" column="id"/>
resultMap>
<select id="getStudentByTeacherId" resultType="Student">
select * from student where tid=#{id}
select>
@Test
public void getTeacherById2() {
SqlSession sqlSession = MybatisUtils.getSqlSession();
TeacherMapper mapper = sqlSession.getMapper(TeacherMapper.class);
Teacher teacher = mapper.getTeacherById2(1);
System.out.println(teacher);
sqlSession.close();
}
输出:
Opening JDBC Connection
Created connection 931675031.
==> Preparing: select * from mybatis.teacher where id=?
==> Parameters: 1(Integer)
<== Columns: id, name
<== Row: 1, 秦老师
====> Preparing: select * from student where tid=?
====> Parameters: 1(Integer)
<==== Columns: id, name, tid
<==== Row: 1, 小明, 1
<==== Row: 2, 小红, 1
<==== Row: 3, 小张, 1
<==== Row: 4, 小李, 1
<==== Row: 5, 小王, 1
<==== Total: 5
<== Total: 1
Teacher{id=0, name='秦老师', students=[Student{id=1, name='小明', tid=1}, Student{id=2, name='小红', tid=1}, Student{id=3, name='小张', tid=1}, Student{id=4, name='小李', tid=1}, Student{id=5, name='小王', tid=1}]}
Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@37883b97]
Returned connection 931675031 to pool.
关联:association【多对一】
集合:collection 【一对多】
association是用于一对一和多对一,而collection是用于一对多的关系
javaType & ofType
JavaType是用来指定pojo中属性的类型
ofType指定的是映射到list集合属性中pojo的类型。
注意说明:
1、保证SQL的可读性,尽量通俗易懂
2、根据实际要求,尽量编写性能更高的SQL语句
3、注意属性名和字段不一致的问题
4、注意一对多和多对一 中:字段和属性对应的问题
5、尽量使用Log4j,通过日志来查看自己的错误
面试高频:
Mysql引擎
InnoDB底层原理
索引
索引优化
什么是动态SQL:动态SQL指的是根据不同的查询条件 , 生成不同的Sql语句.
类似JSTL标签
官网描述:
MyBatis 的强大特性之一便是它的动态 SQL。如果你有使用 JDBC 或其它类似框架的经验,你就能体会到根据不同条件拼接 SQL 语句的痛苦。例如拼接时要确保不能忘记添加必要的空格,还要注意去掉列表最后一个列名的逗号。利用动态 SQL 这一特性可以彻底摆脱这种痛苦。
虽然在以前使用动态 SQL 并非一件易事,但正是 MyBatis 提供了可以被用在任意 SQL 映射语句中的强大的动态 SQL 语言得以改进这种情形。
动态 SQL 元素和 JSTL 或基于类似 XML 的文本处理器相似。在 MyBatis 之前的版本中,有很多元素需要花时间了解。MyBatis 3 大大精简了元素种类,现在只需学习原来一半的元素便可。MyBatis 采用功能强大的基于 OGNL 的表达式来淘汰其它大部分元素。
我们之前写的 SQL 语句都比较简单,如果有比较复杂的业务,我们需要写复杂的 SQL 语句,往往需要拼接,而拼接 SQL ,稍微不注意,由于引号,空格等缺失可能都会导致错误。
那么怎么去解决这个问题呢?这就要使用 mybatis 动态SQL,通过 if, choose, when, otherwise, trim, where, set, foreach等标签,可组合成非常灵活的SQL语句,从而在提高 SQL 语句的准确性的同时,也大大提高了开发人员的效率。
字段:id,title,author,create_time,views
CREATE TABLE `blog` (
`id` varchar(50) NOT NULL COMMENT '博客id',
`title` varchar(100) NOT NULL COMMENT '博客标题',
`author` varchar(30) NOT NULL COMMENT '博客作者',
`create_time` datetime NOT NULL COMMENT '创建时间',
`views` int(30) NOT NULL COMMENT '浏览量'
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
public class IDUtil {
public static String genId(){
return UUID.randomUUID().toString().replaceAll("-","");
}
}
注意:Date类为java.util.Date
,不是java.sql.Date
import java.util.Date;
public class Blog {
private String id;
private String title;
private String author;
private Date createTime;
private int views;
//set,get....
}
public interface BlogMapper {
}
<?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="com.kuang.mapper.BlogMapper">
</mapper>
<settings>
<setting name="mapUnderscoreToCamelCase" value="true"/>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
<!--注册Mapper.xml-->
<mappers>
<mapper resource="com/kuang/dao/BlogMapper.xml"/>
</mappers>
编写接口
//新增一个博客
int addBlog(Blog blog);
映射文件
<insert id="addBlog" parameterType="blog">
insert into blog (id, title, author, create_time, views)
values (#{id},#{title},#{author},#{createTime},#{views});
</insert>
初始化博客方法
import com.kuang.dao.BlogMapper;
import com.kuang.pojo.Blog;
import com.kuang.utils.IDUtil;
import com.kuang.utils.MybatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
import java.util.Date;
public class MyTest {
@Test
public void addInitBlog(){
SqlSession session = MybatisUtils.getSqlSession();
BlogMapper mapper = session.getMapper(BlogMapper.class);
Blog blog = new Blog();
blog.setId(IDUtil.genId());
blog.setTitle("Mybatis如此简单");
blog.setAuthor("狂神说");
blog.setCreateTime(new Date());
blog.setViews(9999);
mapper.addBlog(blog);
blog.setId(IDUtil.genId());
blog.setTitle("Java如此简单");
mapper.addBlog(blog);
blog.setId(IDUtil.genId());
blog.setTitle("Spring如此简单");
mapper.addBlog(blog);
blog.setId(IDUtil.genId());
blog.setTitle("微服务如此简单");
mapper.addBlog(blog);
session.close();
}
}
初始化数据完毕!
使用动态 SQL 最常见情景是根据条件包含 where 子句的一部分。
可以实现按不同列搜索的功能(类似方法重载实现的效果)
List<Blog> queryBlogIF(Map map);
<select id="queryBlogIF" parameterType="map">
select * from mybatis.blog where 1=1
<if test="title != null">
and title=#{title}
if>
<if test="author != null">
and author=#{author}
if>
select>
这条语句提供了可选的查找文本功能。如果不传入 “title”和“author”,那么所有BLOG 都会返回;如果传入了 “title” 参数,那么就会对 “title” 一列进行查找并返回对应的 BLOG 结果(细心的读者可能会发现,“title” 的参数值需要包含查找掩码或通配符字符);如果传入了 “author” 参数,那么就会对 “author” 一列进行查找并返回对应的 BLOG 结果;如果都传,也返回相应的结果
@Test
public void queryBlog(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
BlogMapper mapper=sqlSession.getMapper(BlogMapper.class);
Map map=new HashMap();
map.put("title","Mybatis如此简单");
// map.put("author","狂神说");
List<Blog> blogList = mapper.queryBlogIF(map);
for (Blog blog : blogList) {
System.out.println(blog);
}
sqlSession.close();
}
where 元素只会在子元素返回任何内容的情况下才插入 “WHERE” 关键字。
若子句的开头为 “AND” 或 “OR”,where 元素也会视情况将它们去除或保留。
<select id="queryBlogIF" parameterType="map">
select * from mybatis.blog where
select * from mybatis.blog
<where>
<if test="title != null">
title=#{title}
if>
<if test="author != null">
and author=#{author}
if>
where>
select>
set 元素会动态地在行首插入 SET 关键字,并会删掉额外的逗号(这些逗号是在使用条件语句给列赋值时引入的)。
int updateBlog(Map map);
<update id="updateBlog" parameterType="map" >
update blog
<set>
<if test="title != null">
title=#{title},
if>
<if test="author != null">
author=#{author}
if>
set>
where id={id}
update>
@Test
public void updateBlog(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
BlogMapper mapper=sqlSession.getMapper(BlogMapper.class);
Map map=new HashMap();
map.put("title","Mybatis如此简单2");
map.put("author","狂神说");
map.put("id","5482dbf65d264012833e78b74e9fd95b");
mapper.updateBlog(map);
sqlSession.close();
}
只传title,运行正常
只传author,运行正常
传title和author,运行正常
不传titl和author,报错
<trim prefix="" suffix="" suffixOverrides="" prefixOverrides="">trim>
参数说明
prefix:给 trim 标签内 sql 语句加上前缀
suffix:给 trim 标签内 sql 语句加上后缀
prefixOverrides:去除多余的前缀内容,如:prefixOverrides=“OR”,去除 trim 标签内 sql 语句多余的前缀 “OR”
suffixOverrides:去除多余的后缀内容,如:suffixOverrides=“,”,去除 trim 标签内 sql 语句多余的后缀 “,”
如果 where 元素与你期望的不太一样,你也可以通过自定义 trim 元素来定制 where 元素的功能。比如,和 where 元素等价的自定义 trim 元素为:
<trim prefix="WHERE" prefixOverrides="AND |OR ">
...
trim>
prefixOverrides 属性会忽略通过管道符分隔的文本序列(注意此例中的空格是必要的)。上述例子会移除所有 prefixOverrides 属性中指定的内容,并且插入 prefix 属性中指定的内容。
你可以通过使用trim元素来达到中同样的效果:
<trim prefix="SET" suffixOverrides=",">
...
trim>
注意,我们覆盖了后缀值设置,并且自定义了前缀值。
类似与switch…case…default
List<Blog> queryBlogChoose(Map map);
<select id="queryBlogChoose" parameterType="map" resultType="blog">
select * from mybatis.blog
<choose>
<when test="title != null">
title=#{title}
when>
<when test="author != null">
author=#{author}
when>
<otherwise>
and views=#{views}
otherwise>
choose>
select>
@Test
public void queryBlogChoose(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
BlogMapper mapper=sqlSession.getMapper(BlogMapper.class);
Map map=new HashMap();
// map.put("title","Mybatis如此简单");
// map.put("author","狂神说");
map.put("views",9999);
List<Blog> blogList = mapper.queryBlogChoose(map);
for (Blog blog : blogList) {
System.out.println(blog);
}
sqlSession.close();
}
不传参数时,正常运行
传title和author时,正常运行
传author时,正常运行
将blog表中的id改为1,2,3,4
//查询1,2,3号记录的博客
List<Blog> queryBlogForeach(Map map);
<select id="queryBlogForeach" parameterType="map" resultType="blog">
select * from blog
<where>
<foreach collection="ids" item="id" open="and (" close=")" separator="or">
id=#{id}
foreach>
where>
select>
@Test
public void queryBlogForeach(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
BlogMapper mapper=sqlSession.getMapper(BlogMapper.class);
Map map=new HashMap();
ArrayList<Integer> ids = new ArrayList<>();
ids.add(1);
ids.add(2);
ids.add(3);
map.put("ids",ids);
mapper.queryBlogForeach(map);
sqlSession.close();
}
集合为空时:
集合中添加1个id时(where标签智能去除了开头的and)
集合中添加3个id时
将需要重复编写的sql片段提取出来方便复用
<sql id="if-title-author">
<if test="title != null">
title=#{title},
if>
<if test="author != null">
author=#{author}
if>
sql>
<select id="queryBlogIF" parameterType="map" resultType="com.kuang.pojo.Blog">
select * from mybatis.blog
<where>
<include refid="if-title-author"/>
where>
select>
动态SQL就是在拼接SQL语句,我们只要保证SQL的正确性,按照SQL的语法格式,去排列组合就可以了。
建议:先在MYSQL中写出完整的SQL再对应的去修改成动态SQL实现
1、什么是缓存 [ Cache ]?
存在内存中的临时数据。
将用户经常查询的数据放在缓存(内存)中,用户去查询数据就不用从磁盘上(关系型数据库数据文件)查询,从缓存中查询,从而提高查询效率,解决了高并发系统的性能问题。
2、为什么使用缓存?
3、什么样的数据能使用缓存?
MyBatis包含一个非常强大的查询缓存特性,它可以非常方便地定制和配置缓存。缓存可以极大的提升查询效率。
MyBatis系统中默认定义了两级缓存:一级缓存和二级缓存
默认情况下,只有一级缓存开启。(SqlSession级别的缓存,也称为本地缓存)
二级缓存需要手动开启和配置,他是基于namespace级别的缓存。
为了提高扩展性,MyBatis定义了缓存接口Cache。我们可以通过实现Cache接口来自定义二级缓存
一级缓存也叫本地(会话)缓存:
与数据库同一次会话(sqlSession)期间查询到的数据会放在本地缓存中。
以后如果需要获取相同的数据,直接从缓存中拿,没必须再去查询数据库;
1、在mybatis中加入日志,方便测试结果
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
settings>
2、编写接口方法
//根据id查询用户
User queryUserById(@Param("id") int id);
3、接口对应的Mapper文件
<select id="queryUserById" resultType="user">
select * from user where id = #{id}
select>
4、测试
@Test
public void testQueryUserById(){
SqlSession session = MybatisUtils.getSqlSession();
UserMapper mapper = session.getMapper(UserMapper.class);
User user = mapper.queryUserById(1);
System.out.println(user);
//查询同一个用户
User user2 = mapper.queryUserById(1);
System.out.println(user2);
System.out.println(user==user2);
session.close();
}
5、结果分析:可以看到只运行了一次sql
一级缓存是SqlSession级别的缓存,是一直开启的,我们关闭不了它;
一级缓存失效情况:没有使用到当前的一级缓存,效果就是,还需要再向数据库中发起一次查询请求!
1.查询不同的记录
2,增删改操作,可能会改变原来的数据,所以必定会刷新缓存!
3.查询不同的Mapper.xml
4.手动清理缓存!
1、sqlSession不同
@Test
public void testQueryUserById(){
SqlSession session = MybatisUtils.getSqlSession();
SqlSession session2 = MybatisUtils.getSqlSession();
UserMapper mapper = session.getMapper(UserMapper.class);
UserMapper mapper2 = session2.getMapper(UserMapper.class);
User user = mapper.queryUserById(1);
System.out.println(user);
User user2 = mapper2.queryUserById(1);
System.out.println(user2);
System.out.println(user==user2);
session.close();
session2.close();
}
观察结果:发现发送了两条SQL语句!
结论:每个sqlSession中的缓存相互独立
2、sqlSession相同,查询条件不同
@Test
public void testQueryUserById(){
SqlSession session = MybatisUtils.getSqlSession();
UserMapper mapper = session.getMapper(UserMapper.class);
UserMapper mapper2 = session.getMapper(UserMapper.class);
User user = mapper.queryUserById(1);
System.out.println(user);
User user2 = mapper2.queryUserById(2);
System.out.println(user2);
System.out.println(user==user2);
session.close();
}
观察结果:发现发送了两条SQL语句!很正常的理解
结论:当前缓存中,不存在这个数据
3、sqlSession相同,两次查询之间执行了增删改操作!
增加方法
//修改用户
int updateUser(Map map);
编写SQL
<update id="updateUser" parameterType="map">
update user set name = #{name} where id = #{id}
</update>
测试
@Test
public void testQueryUserById(){
SqlSession session = MybatisUtils.getSqlSession();
UserMapper mapper = session.getMapper(UserMapper.class);
//第一次查询
User user = mapper.queryUserById(1);
System.out.println(user);
//更新另一条记录
HashMap map = new HashMap();
map.put("name","kuangshen");
map.put("id",4);
mapper.updateUser(map);
//第二次查询同一id记录
User user2 = mapper.queryUserById(1);
System.out.println(user2);
System.out.println(user==user2);
session.close();
}
观察结果:查询在中间执行了增删改操作后,重新执行了
结论:因为增删改操作可能会对当前数据产生影响
4、sqlSession相同,手动清除一级缓存
@Test
public void testQueryUserById(){
SqlSession session = MybatisUtils.getSqlSession();
UserMapper mapper = session.getMapper(UserMapper.class);
User user = mapper.queryUserById(1);
System.out.println(user);
System.out.println("===================");
session.clearCache();//手动清除缓存
User user2 = mapper.queryUserById(1);
System.out.println(user2);
System.out.println(user==user2);
session.close();
}
一级缓存就是一个map
小结:一级缓存默认是开启的,只在一次SqlSession中有效,也就是拿到连接到关闭连接这个区间段(相当于一个用户不断查询相同的数据,比如不断刷新),一级缓存就是一个map
二级缓存也叫全局缓存,一级缓存作用域太低了,所以诞生了二级缓存
基于namespace级别的缓存,一个名称空间,对应一个二级缓存;
工作机制
一个会话查询一条数据,这个数据就会被放在当前会话的一级缓存中;
如果当前会话关闭了,这个会话对应的一级缓存就没了;但是我们想要的是,会话关闭了,一级缓存中的数据被保存到二级缓存中;
新的会话查询信息,就可以从二级缓存中获取内容;
不同的mapper查出的数据会放在自己对应的缓存(map)中;
1、开启全局缓存 【mybatis-config.xml】
<setting name="cacheEnabled" value="true"/>
2、去每个mapper.xml中配置使用二级缓存,这个配置非常简单【xxxMapper.xml】
可以不加属性,也可以自定义属性
<cache/>
官方示例=====>查看官方文档
<cache
eviction="FIFO"
flushInterval="60000"
size="512"
readOnly="true"/>
这个更高级的配置创建了一个 FIFO 缓存,每隔 60 秒刷新,最多可以存储结果对象或列表的 512 个引用,而且返回的对象被认为是只读的,因此对它们进行修改可能会在不同线程中的调用者产生冲突。
3、代码测试
实体类要先实现序列化接口,否则报错:Cause: java.io.NotSerializableException: com.kuang.pojo.User
测试代码
@Test
public void testQueryUserById(){
SqlSession session = MybatisUtils.getSession();
SqlSession session2 = MybatisUtils.getSession();
UserMapper mapper = session.getMapper(UserMapper.class);
UserMapper mapper2 = session2.getMapper(UserMapper.class);
User user = mapper.queryUserById(1);
System.out.println(user);
session.close();
User user2 = mapper2.queryUserById(1);
System.out.println(user2);
System.out.println(user==user2);
session2.close();
}
开启二级缓存前
开启二级缓存后
为什么还是false?视频中是true
使用以下配置开启对应映射文件的二级缓存
<cache
eviction="FIFO"
flushInterval="60000"
size="512"
readOnly="true"/>
只要开启了二级缓存,我们在同一个Mapper中的查询,可以在二级缓存中拿到数据
查出的数据都会被默认先放在一级缓存中
只有会话提交或者关闭以后,一级缓存中的数据才会转到二级缓存中
缓存顺序:
1 先看二级缓存中有没有
再看一级缓存中有没有
查询数据库
注:一二级缓存都没有,查询数据库,查询后将数据放入一级缓存
第三方缓存实现–EhCache: 查看百度百科
介绍:
EhCache 是一个纯Java的进程内缓存框架,具有快速、精干等特点,是Hibernate中默认的CacheProvider
Ehcache是一种广泛使用的开源Java分布式缓存。主要面向通用缓存
Enchache可以使用配置文件ehcache.xml
使用方法
<dependency>
<groupId>org.mybatis.cachesgroupId>
<artifactId>mybatis-ehcacheartifactId>
<version>1.2.3version>
dependency>
<mapper namespace = "com.kuang.dao.UserMapper" >
<cache type="org.mybatis.caches.ehcache.LoggingEhcache" />
<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>
mapper>
在src/main/resources/下创建编写ehcache.xml文件,如果在加载时未找到ehcache.xml资源或出现问题,则将使用默认配置。
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"
updateCheck="false">
<diskStore path="./tmpdir/Tmp_EhCache"/>
<defaultCache
eternal="false"
maxElementsInMemory="10000"
overflowToDisk="false"
diskPersistent="false"
timeToIdleSeconds="1800"
timeToLiveSeconds="259200"
memoryStoreEvictionPolicy="LRU"/>
<cache
name="cloud_user"
eternal="false"
maxElementsInMemory="5000"
overflowToDisk="false"
diskPersistent="false"
timeToIdleSeconds="1800"
timeToLiveSeconds="1800"
memoryStoreEvictionPolicy="LRU"/>
ehcache>
合理的使用缓存,可以让我们程序的性能大大提升!
Cache接口
package com.kuang.pojo;
import org.apache.ibatis.cache.Cache;
import java.util.concurrent.locks.ReadWriteLock;
public class MyCache implements Cache {
@Override
public String getId() {
return null;
}
@Override
public void putObject(Object key, Object value) {
}
@Override
public Object getObject(Object key) {
return null;
}
@Override
public Object removeObject(Object key) {
return null;
}
@Override
public void clear() {
}
@Override
public int getSize() {
return 0;
}
@Override
public ReadWriteLock getReadWriteLock() {
return null;
}
}
<mapper namespace = "com.kuang.pojo.UserMapper" >
<cache type="com.kuang.pojo.MyCache"/>
mapper>
这部分关于缓存的内容了解就可以,以后开发我们会用Redis数据库来做缓存!K-V