文章目录
简介
什么是Mybatis
- MyBatis 是一款优秀的持久层框架
- 它支持定制化 SQL、存储过程以及高级映射
- MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集
- MyBatis 可以使用简单的XML 或注解来配置和映射原生类型、接口和 Java 的 POJO (Plain Old Java Objects,普通老式Java 对象)为数据库中的记录
- MyBatis 本是Apache的一个开源项目Batis,2010年这个项目由apache software foundation 迁移到了google code,并且改名为MyBatis
- 2013年11月迁移到Github
持久化
持久化就是将程序的数据在持久状态和瞬时状态转化的过程
- 内存:断电即失
- 数据库(jdbc):IO文件持久化
为什么需要需要持久化?
- 有一些对象,不能让他丢掉
- 内存太贵了,一直存在内存里面,经费消耗严重
持久层
Dao层,Service层,Controller层…
- 完成持久化工作的代码块
- 层界限十分明显
为什么需要Mybatis
- 帮助程序猿将数据存入到数据库中
- 方便
- 传统的JDBC代码太复杂了。简化、自动化
- 不用Mybatis也可以,技术没有高低之分
【优点】
简单易学
:本身就很小且简单。没有任何第三方依赖,最简单安装只要两个jar文件+配置几个sql映射文件易于学习,易于使用,通过文档和源代码,可以比较完全的掌握它的设计思路和实现灵活
:mybatis不会对应用程序或者数据库的现有设计强加任何影响。sql写在xml里,便于统一管理和优化。通过sql语句可以满足操作数据库的所有需求解除sql与程序代码的耦合
:通过提供DAO层,将业务逻辑和数据访问逻辑分离,使系统的设计更清晰,更易维护,更易单元测试。sql和代码的分离,提高了可维护性提供映射标签,支持对象与数据库的orm字段关系映射
提供对象关系映射标签,支持对象关系组建维护
提供xml标签,支持编写动态sql
(循环拼接)
依赖
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.3</version>
</dependency>
第一个Mybatis程序
创建数据库
CREATE DATABASE `mybatis-study`;
USE `mybatis-study`;
CREATE TABLE `user` (
`id` INT NOT NULL PRIMARY KEY,
`name` VARCHAR(30) DEFAULT NULL,
`pwd` VARCHAR(30) DEFAULT NULL
) ENGINE=INNODB DEFAULT CHARSET=utf8;
INSERT INTO `user` ( `id`, `name`, `pwd` )
VALUES
( 1, '张三', '123456' ),
( 2, '李四', '123456' ),
( 3, '王五', '123456' )
创建一个maven项目
修改pom.xml文件
<dependencies>
<!-- mybatis -->
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.1</version>
</dependency>
<!--junit单元测试-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<!--mysql驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.29</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.26</version>
</dependency>
</dependencies>
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.cj.jdbc.Driver"/>
<property name="url"
value="jdbc:mysql://localhost:3306/mybatis-study?useSSL=false&useUnicode=true&characterEncoding=UTF-8"/>
<property name="username" value="root"/>
<property name="password" value="12345678"/>
</dataSource>
</environment>
</environments>
<!-- 每一个Mapper.xml都需要在MyBatis核心配置文件中注册-->
<mappers>
<mapper resource="org/dam/dao/xml/UserMapper.xml"/>
</mappers>
</configuration>
mybatis工具类
package org.dam.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;
/**
* @Author dam
* @create 2023/12/20 11:11
*/
public class MybatisUtils {
private static SqlSessionFactory sqlSessionFactory;
static {
try {
//使用mybatis获取sqlSessionFactory对象
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 可以从SqlSessionFactory中获得 SqlSession 的实例
* SqlSession 提供了在数据库执行 SQL 命令所需的所有方法
* @return
*/
public static SqlSession getSqlSession() {
return sqlSessionFactory.openSession();
}
}
实体类
package org.dam.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @Author dam
* @create 2023/12/20 11:13
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private int id;
private String name;
private String pwd;
}
接口
package org.dam.dao;
import org.dam.entity.User;
import java.util.List;
/**
* @Author dam
* @create 2023/12/20 11:15
*/
public interface UserDao {
List<User> getUserList();
}
mapper
- 需要指定好对应的接口,这个mapper就相当于是接口的实现类
- namespace中的包名要和Dao/mapper的包名一致
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- namespace=绑定一个Dao/Mapper接口 -->
<mapper namespace="org.dam.dao.UserDao">
<!-- select查询语句,ID对应方法名 -->
<select id="getUserList" resultType="org.dam.entity.User">
select * from user
</select>
</mapper>
项目结构
单元测试
报错
/Library/Java/JavaVirtualMachines/zulu-8.jdk/Contents/Home/bin/java -ea -Didea.test.cyclic.buffer.size=1048576 -javaagent:/Applications/IntelliJ IDEA.app/Contents/lib/idea_rt.jar=63592:/Applications/IntelliJ IDEA.app/Contents/bin -Dfile.encoding=UTF-8 -classpath /Applications/IntelliJ IDEA.app/Contents/lib/idea_rt.jar:/Applications/IntelliJ IDEA.app/Contents/plugins/junit/lib/junit5-rt.jar:/Applications/IntelliJ IDEA.app/Contents/plugins/junit/lib/junit-rt.jar:/Library/Java/JavaVirtualMachines/zulu-8.jdk/Contents/Home/jre/lib/cat.jar:/Library/Java/JavaVirtualMachines/zulu-8.jdk/Contents/Home/jre/lib/charsets.jar:/Library/Java/JavaVirtualMachines/zulu-8.jdk/Contents/Home/jre/lib/ext/cldrdata.jar:/Library/Java/JavaVirtualMachines/zulu-8.jdk/Contents/Home/jre/lib/ext/crs-agent.jar:/Library/Java/JavaVirtualMachines/zulu-8.jdk/Contents/Home/jre/lib/ext/dnsns.jar:/Library/Java/JavaVirtualMachines/zulu-8.jdk/Contents/Home/jre/lib/ext/jaccess.jar:/Library/Java/JavaVirtualMachines/zulu-8.jdk/Contents/Home/jre/lib/ext/localedata.jar:/Library/Java/JavaVirtualMachines/zulu-8.jdk/Contents/Home/jre/lib/ext/nashorn.jar:/Library/Java/JavaVirtualMachines/zulu-8.jdk/Contents/Home/jre/lib/ext/sunec.jar:/Library/Java/JavaVirtualMachines/zulu-8.jdk/Contents/Home/jre/lib/ext/sunjce_provider.jar:/Library/Java/JavaVirtualMachines/zulu-8.jdk/Contents/Home/jre/lib/ext/sunpkcs11.jar:/Library/Java/JavaVirtualMachines/zulu-8.jdk/Contents/Home/jre/lib/ext/zipfs.jar:/Library/Java/JavaVirtualMachines/zulu-8.jdk/Contents/Home/jre/lib/jce.jar:/Library/Java/JavaVirtualMachines/zulu-8.jdk/Contents/Home/jre/lib/jfr.jar:/Library/Java/JavaVirtualMachines/zulu-8.jdk/Contents/Home/jre/lib/jsse.jar:/Library/Java/JavaVirtualMachines/zulu-8.jdk/Contents/Home/jre/lib/management-agent.jar:/Library/Java/JavaVirtualMachines/zulu-8.jdk/Contents/Home/jre/lib/resources.jar:/Library/Java/JavaVirtualMachines/zulu-8.jdk/Contents/Home/jre/lib/rt.jar:/Users/dam/Projects/study/mybatis-study/target/test-classes:/Users/dam/Projects/study/mybatis-study/target/classes:/Users/dam/Dev/Maven/apache-maven-3.9.5/maven-repository/org/mybatis/mybatis/3.5.1/mybatis-3.5.1.jar:/Users/dam/Dev/Maven/apache-maven-3.9.5/maven-repository/junit/junit/4.12/junit-4.12.jar:/Users/dam/Dev/Maven/apache-maven-3.9.5/maven-repository/org/hamcrest/hamcrest-core/1.3/hamcrest-core-1.3.jar:/Users/dam/Dev/Maven/apache-maven-3.9.5/maven-repository/mysql/mysql-connector-java/8.0.29/mysql-connector-java-8.0.29.jar:/Users/dam/Dev/Maven/apache-maven-3.9.5/maven-repository/com/google/protobuf/protobuf-java/3.19.4/protobuf-java-3.19.4.jar:/Users/dam/Dev/Maven/apache-maven-3.9.5/maven-repository/org/projectlombok/lombok/1.18.26/lombok-1.18.26.jar com.intellij.rt.junit.JUnitStarter -ideVersion5 -junit4 org.dam.UserDaoTest,test
java.lang.ExceptionInInitializerError
at org.dam.UserDaoTest.test(UserDaoTest.java:20)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:69)
at com.intellij.rt.junit.IdeaTestRunner$Repeater$1.execute(IdeaTestRunner.java:38)
at com.intellij.rt.execution.junit.TestsRepeater.repeat(TestsRepeater.java:11)
at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:35)
at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:232)
at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:55)
Caused by: org.apache.ibatis.exceptions.PersistenceException:
### Error building SqlSession.
### The error may exist in org/dam/dao/xml/UserMapper.xml
### Cause: org.apache.ibatis.builder.BuilderException: Error parsing SQL Mapper Configuration. Cause: java.io.IOException: Could not find resource org/dam/dao/xml/UserMapper.xml
at org.apache.ibatis.exceptions.ExceptionFactory.wrapException(ExceptionFactory.java:30)
at org.apache.ibatis.session.SqlSessionFactoryBuilder.build(SqlSessionFactoryBuilder.java:80)
at org.apache.ibatis.session.SqlSessionFactoryBuilder.build(SqlSessionFactoryBuilder.java:64)
at org.dam.utils.MybatisUtils.<clinit>(MybatisUtils.java:24)
... 25 more
Caused by: org.apache.ibatis.builder.BuilderException: Error parsing SQL Mapper Configuration. Cause: java.io.IOException: Could not find resource org/dam/dao/xml/UserMapper.xml
at org.apache.ibatis.builder.xml.XMLConfigBuilder.parseConfiguration(XMLConfigBuilder.java:121)
at org.apache.ibatis.builder.xml.XMLConfigBuilder.parse(XMLConfigBuilder.java:98)
at org.apache.ibatis.session.SqlSessionFactoryBuilder.build(SqlSessionFactoryBuilder.java:78)
... 27 more
Caused by: java.io.IOException: Could not find resource org/dam/dao/xml/UserMapper.xml
at org.apache.ibatis.io.Resources.getResourceAsStream(Resources.java:114)
at org.apache.ibatis.io.Resources.getResourceAsStream(Resources.java:100)
at org.apache.ibatis.builder.xml.XMLConfigBuilder.mapperElement(XMLConfigBuilder.java:371)
at org.apache.ibatis.builder.xml.XMLConfigBuilder.parseConfiguration(XMLConfigBuilder.java:119)
... 29 more
Process finished with exit code 255
原因应该是没有扫描到mapper文件,在pom文件里面添加如下配置
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
</resource>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
</resource>
</resources>
</build>
增删改查实现
常规增删改查
接口
package org.dam.dao;
import org.dam.entity.User;
import java.util.List;
import java.util.Map;
/**
* @Author dam
* @create 2023/12/20 11:15
*/
public interface UserDao {
List<User> getUserList();
//根据id查询用户
User selectUserById(int id);
//添加一个用户
int addUser(User user);
//修改一个用户
int updateUser(User user);
//根据id删除用户
int deleteUser(int id);
}
mapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- namespace=绑定一个Dao/Mapper接口 -->
<mapper namespace="org.dam.dao.UserDao">
<!-- select查询语句,ID对应方法名 -->
<select id="getUserList" resultType="org.dam.entity.User">
select * from user
</select>
<select id="selectUserById" resultType="org.dam.entity.User">
select * from user where id = #{id}
</select>
<insert id="addUser" parameterType="org.dam.entity.User">
insert into user (id,name,pwd) values (#{id},#{name},#{pwd})
</insert>
<update id="updateUser" parameterType="org.dam.entity.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>
单元测试
package org.dam;
import org.apache.ibatis.session.SqlSession;
import org.dam.dao.UserDao;
import org.dam.entity.User;
import org.dam.utils.MybatisUtils;
import org.junit.Test;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @Author dam
* @create 2023/12/20 11:18
*/
public class UserDaoTest {
@Test
public void test(){
//第一步:获得SqlSession对象
SqlSession sqlSession = MybatisUtils.getSqlSession();
try{
//执行sql
//方式一getMapper
UserDao mapper = sqlSession.getMapper(UserDao.class);
List<User> userList = mapper.getUserList();
for (User user : userList) {
System.out.println(user);
}
}
catch (Exception e){
e.printStackTrace();
}
finally {
//关闭SqlSession
sqlSession.close();
}
}
@Test
public void setSelectUserById() {
SqlSession session = MybatisUtils.getSqlSession(); //获取SqlSession连接
UserDao mapper = session.getMapper(UserDao.class);
User user = mapper.selectUserById(1);
System.out.println(user);
session.close();
}
@Test
public void testAddUser() {
SqlSession session = MybatisUtils.getSqlSession();
UserDao mapper = session.getMapper(UserDao.class);
User user = new User(5,"王五","zxcvbn");
int i = mapper.addUser(user);
System.out.println(i);
session.commit(); //提交事务,重点!不写的话不会提交到数据库
session.close();
}
@Test
public void testUpdateUser() {
SqlSession session = MybatisUtils.getSqlSession();
UserDao mapper = session.getMapper(UserDao.class);
User user = mapper.selectUserById(1);
user.setPwd("asdfgh");
int i = mapper.updateUser(user);
System.out.println(i);
session.commit(); //提交事务,重点!不写的话不会提交到数据库
session.close();
}
@Test
public void testDeleteUser() {
SqlSession session = MybatisUtils.getSqlSession();
UserDao mapper = session.getMapper(UserDao.class);
int i = mapper.deleteUser(5);
System.out.println(i);
session.commit(); //提交事务,重点!不写的话不会提交到数据库
session.close();
}
}
注意(防SQL注入)
本来只是让用户查询自己的数据
select * from user where id = #{id}
但是如果用户传入的id是"1 or 1=1",拼接出来的sql就变成了如下样式,所有用户数据就被拿走了
select from user where id = 1 or 1=1
避免方式:对数据做严格校验
使用字典来传参
假设我们的实体类,或者数据库中的表,字段或者参数过多,我们可以考虑使用Map!
Map传递参数,直接在sql中取出key即可!【parameterType=“map”】
对象传递参数,直接在sql中取对象的属性即可!【parameterType=“Object’”】
接口
//增加一个用户
int addUserT(Map<String,Object> map);
mapper
<insert id="addUserT" parameterType="map">
insert into user (id, name, pwd)
values (#{userid}, #{username}, #{password});
</insert>
单元测试
@Test
public void addUserT() {
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserDao mapper = sqlSession.getMapper(UserDao.class);
Map<String, Object> map = new HashMap<String, Object>();
map.put("userid", 5);
map.put("username", "hello");
map.put("password", "1231231");
mapper.addUserT(map);
sqlSession.commit();
sqlSession.close();
}
模糊查询
查询“李”字,应该要查出两个人
接口
List<User> getUserLike(String value);
mapper
<select id="getUserLike" resultType="org.dam.entity.User">
select *
from user
where name like #{name}
</select>
单元测试
@Test
public void getUserLike() {
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserDao mapper = sqlSession.getMapper(UserDao.class);
List<User> userLike = mapper.getUserLike("%李%");
for (User user : userLike) {
System.out.println(user.toString());
}
sqlSession.close();
}
模糊查询的两种实现方式
Mybatis配置文件
核心配置文件
- mybatis-config.xml 是系统核心配置文件
- MyBatis 的配置文件包含了会深深影响 MyBatis 行为的设置和属性信息
能配置的内容如下:
configuration(配置)
properties(属性)
settings(设置)
typeAliases(类型别名)
typeHandlers(类型处理器)
objectFactory(对象工厂)
plugins(插件)
environments(环境配置)
environment(环境变量)
transactionManager(事务管理器)
dataSource(数据源)
databaseIdProvider(数据库厂商标识)
mappers(映射器)
<!-- 注意元素节点的顺序!顺序不对会报错 -->
环境配置
- MyBatis可以有多种配置适应多种环境,尽管可以配置多个环境,但每个SqlSessionFactory实例只能选择一种环境。
- 配置MyBatis的多套运行环境,将SQL映射到多个不同的数据库上,必须指定其中一个为默认运行环境(通过default指定)
- 一个environment标签内是一套具体的环境,通过id来区分,id要保证唯一
<environments default="development">
<environment id="development">
<transactionManager type="JDBC">
<property name="..." value="..."/>
</transactionManager>
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
Mybatis默认的事务管理器就是JDBC,默认连接池是POOLED
子元素节点:transactionManager - [ 事务管理器 ]
<transactionManager type="[ JDBC | MANAGED ]"/>
子元素节点:数据源(dataSource)
- dataSource 元素使用标准的 JDBC 数据源接口来配置 JDBC 连接对象的资源
- 数据源是必须配置的
有三种内建的数据源类型,type=“[UNPOOLED|POOLED|JNDI]”
- UNPOOLED: 这个数据源的实现只是每次被请求时打开和关闭连接。虽然有点慢,但对于在数据库连接可用性方面没有太高要求的简单应用程序来说,是一个很好的选择。不同的数据库在性能方面的表现也是不一样的,对于某些数据库来说,使用连接池并不重要,这个配置就很适合这种情形。UNPOOLED类型的数据源具有以下属性:
driver-这是JDBC驱动的Java类的完全限定名(并不是JDBC驱动中可能包含的数据源类)
url-这是数据库的JDBC URL地址。
username-登录数据库的用户名。
password-登录数据库的密码。
defaultTransactionIsolationLevel-默认的连接事务隔离级别。
defaultNetworkTimeout-The default network timeout value in milliseconds to wait for the database operation to complete
作为可选项,你也可以传递属性给数据库驱动。只需在属性名加上“driver.”前缀即可,例如:
driver.encoding=UTF8
这将通过DriverManager..getConnection(url,driverProperties)方法传递值为UTE8的encoding属性给数据库驱动。
- POOLED: 这种数据源的实现利用“池”的概念将 JDBC 连接对象组织起来 , 这是一种使得并发 Web 应用快速响应请求的流行处理方式。
- JNDI:这个数据源的实现是为了能在如 Spring 或应用服务器这类容器中使用,容器可以集中或在外部配置数据源,然后放置一个 JNDI 上下文的引用。
数据源也有很多第三方的实现,比如dbcp
,c3p0
,druid
等等….
属性优化
数据库这些属性都是可外部配置且可动态替换的,既可以在典型的 Java 属性文件中配置,亦可通过 properties 元素的子元素来传递。接下来,我们可以优化我们的配置文件
- 在资源目录下新建一个db.properties
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=utf8
- 将文件导入properties 配置文件
<configuration>
<!--方式一:导入properties文件-->
<properties resource="db.properties"/>
<!--方式二:可以在原有文件的基础上,增加一些配置属性,
如db.properties文件中只有driver和url的配置,可在下方配置username和password-->
<properties resource="db.properties">
<property name="username" value="root"/>
<property name="password" value="root"/>
</properties>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="mapper/UserMapper.xml"/>
</mappers>
</configuration>
当然也可以写全,这样就不需要补充了。如果两个地方都写了,会优先使用外部的文件
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=utf8
username=root
password=root
别名优化
方式一:给类取别名
<!--第一种方式:配置别名,注意顺序-->
<typeAliases>
<typeAlias type="com.kuang.pojo.User" alias="User"/>
</typeAliases>
设置完之后就不用写这么长的名字
方式二:给包名取别名
<!--第二种方式:给整个包名取别名,这样当前包下面的类无需单独为其取别名了-->
<typeAliases>
<package name="com.kuang.pojo"/>
</typeAliases>
MyBatis会在包名下面搜索需要的Java Bean,扫描实体类的包,它的默认别名就为这个类的类名,首字母小写!
别名包下的类直接用小写(第一个字母)来就行
- 在实体类比较少的时候,使用第一种方式
- 如果实体类比较多的时候,使用第二种方式
- 第一种可以DIY命名,第二种不行,如果非要改,需要在实体上增加注解
通过注解取别名
@Alias("hello")
public class User {
}
其他设置
可以设置什么
- 懒加载(lazyLoadingEnabled)
- 日志实现(logImpl)
- 缓存开启关闭(cacheEnabled)
一个配置完整的 settings 元素的示例如下:
<settings>
<setting name="cacheEnabled" value="true"/>
<setting name="lazyLoadingEnabled" 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="mapUnderscoreToCamelCase" value="false"/>
<setting name="localCacheScope" value="SESSION"/>
<setting name="jdbcTypeForNull" value="OTHER"/>
<setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/>
</settings>
映射器
方式一:绑定Mapper文件(推荐)
<!--每一个Mapper.xml都需要在MyBatis核心配置文件中注册-->
<mappers>
<mapper resource="org/dam/dao/xml/UserMapper.xml"/>
</mappers>
方式二:使用class文件绑定
<mappers>
<mapper resource="org.dam.dao.UserDao"/>
</mappers>
方式三:直接绑定文件夹
<mappers>
<package name="org.dam.dao"/>
</mappers>
- 接口和他的Mapper配置文件必须同名
- 接口和他的Mapper配置文件必须在同一个包下
生命周期和作用域
生命周期和作用域 是至关重要的,因为错误的使用会导致非常严重的并发问题。
SqlSessionFactoryBuilder
- 一旦创建了SqlSessionFactory,就不再需要它了
SqlSessionFactory
- 说白了就是可以想象为:数据库连接池
- SqlSessionFactory一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建另一个实例
- 因此SqlSessionFactory的最佳作用域是应用作用域。最简单的就是使用单例模式或者静态单例模式
- 程序开始就开始,程序结束才结束
SqlSession
- 连接到连接池的一个请求
- SqlSession的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域
- 用完之后需要赶紧关闭,否则资源被占用
- SqlSession实例的引用可以放在任何类型的托管作用域中,比如Servlet框架中的HttpSession。如果你现在正在使用一种Web框架,要考虑SqlSession放在一个和HTTP请求对象相似的作用域中。换句话说,每次收到的HTTP请求,就可以打开一个SqlSession,返回一个响应,然后就关闭它。这个关闭操作是很重要的,应该把这个关闭操作放到finally块中以确保每次都能执行
- 每一个Mapper,就代表一个具体的任务
ResultMap结果集映射
- 实体类的密码字段名为password,但是数据表的字段名为pwd
- 查出来的数据:User{id=1, name=’狂神’, password=’null’},
select * from user where id = #{id}
可以看做select id,name,pwd from user where id = #{id}
,mybatis会根据这些查询的列名(会将列名转化为小写,数据库不区分大小写) , 去对应的实体类中查找相应列名的set方法设值 , 由于找不到setPwd() , 所以password返回null ; 【自动映射】
方式一:取别名
方式二:使用resultMap
字段一样的不需要映射
自动映射
<select id="selectUserById" resultType="map">
select id , name , pwd
from user
where id = #{id}
</select>
手动映射
定义resultMap的id
<resultMap id="UserMap" type="User">
<!-- id为主键 -->
<id column="id" property="id"/>
<!-- column是数据库表的列名 , property是对应实体类的属性名 -->
<result column="name" property="name"/>
<result column="pwd" property="password"/>
</resultMap>
指定返回类型为resultMap的id
<select id="selectUserById" resultMap="UserMap">
select id , name , pwd from user where id = #{id}
</select>
复杂的结果集映射
一对多和多对一就不会这么简单了,之后会使用到一些高级的结果集映射,association,collection这些
日志工厂
配置使用日志
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settins>
开启日志之后,控制台有输出
日志工具
Mybatis内置的日志工厂提供日志功能,具体的日志实现有以下几种工具:
- SLF4J
- LOG4J【掌握】
- L0G4J2
- JDK_LOGGING
- COMMONS LOGGING
- STDOUT_LOGGING【掌握】
- NO LOGGING
具体选择哪个日志实现工具由MyBatis的内置日志工厂确定。它会使用最先找到的(按上文列举的顺序查找)。 如果一个都未找到,日志功能就会被禁用。
Log4j使用
- Log4j是Apache的一个开源项目,通过使用Log4j,我们可以控制日志信息输送的目的地是控制台、文件、GUI组件
- 我们也可以控制每一条日志的输出格式
- 通过定义每一条日志信息的级别,我们能够更加细致地控制日志的生成过程
- 通过一个配置文生来灵活地进行配置,而不需要修改应用的代码
使用过程
依赖
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
编写配置文件
#将等级为DEBUG的日志信息输出到console和file这两个目的地,console和file的定义在下面的代码
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=[%c]-%m%n
#文件输出的相关设置
log4j.appender.file = org.apache.log4j.RollingFileAppender
log4j.appender.file.File=./log/dam.log
log4j.appender.file.MaxFileSize=10mb
log4j.appender.file.Threshold=DEBUG
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=[%p][%d{yy-MM-dd}][%c]%m%n
#日志输出级别
log4j.logger.org.mybatis=DEBUG
log4j.logger.java.sql=DEBUG
log4j.logger.java.sql.Statement=DEBUG
log4j.logger.java.sql.ResultSet=DEBUG
log4j.logger.java.sql.PreparedStatement=DEBUG
修改Mybatis配置
<settings>
<setting name="logImpl" value="LOG4J"/>
</settings>
启动程序即可
使用log4j的日志输出工具
//注意导包:org.apache.log4j.Logger
static Logger logger = Logger.getLogger(UserDaoTest.class);
@Test
public void selectUser() {
logger.info("info:进入selectUser方法");
logger.debug("debug:进入selectUser方法");
logger.error("error: 进入selectUser方法");
}
Mybatis实现分页
思考:为什么需要分页?
在学习mybatis等持久层框架的时候,会经常对数据进行增删改查操作,使用最多的是对数据库进行查询操作,如果查询大量数据的时候,我们往往使用分页进行查询,也就是每次处理小部分数据,这样对数据库压力就在可控范围内。
写sql可以使用Limit实现分页
# 语法
SELECT * FROM table LIMIT stratIndex,pageSize
# 检索记录行 6-15
SELECT * FROM table LIMIT 5,10;
# 为了检索从某一个偏移量到记录集的结束所有的记录行,可以指定第二个参数为 -1
# 检索记录行 96-last
SELECT * FROM table LIMIT 95,-1;
# 如果只给定一个参数,它表示返回最大的记录行数目
# 检索前 5 个记录行,换句话说,LIMIT n 等价于 LIMIT 0,n。
SELECT * FROM table LIMIT 5;
修改Mapper文件
<select id="selectUser" parameterType="map" resultType="user">
select * from user limit #{startIndex},#{pageSize}
</select>
接口,参数为map
//选择全部用户实现分页
List<User> selectUser(Map<String,Integer> map);
在测试类中传入参数测试
- 推断:起始位置=(当前页面 - 1 ) * 页面大小
//分页查询 , 两个参数startIndex , pageSize
@Test
public void testSelectUser() {
SqlSession session = MybatisUtils.getSession();
UserMapper mapper = session.getMapper(UserMapper.class);
// 第几页
int currentPage = 1;
// 每页显示几个
int pageSize = 2;
Map<String,Integer> map = new HashMap<String,Integer>();
map.put("startIndex",(currentPage-1)*pageSize);
map.put("pageSize",pageSize);
List<User> users = mapper.selectUser(map);
for (User user: users){
System.out.println(user);
}
session.close();
}
使用注解开发(注解不够灵活,推荐还是使用XML)
关于接口的理解
- 接口从更深层次的理解,应是定义(规范,约束)与实现(名实分离的原则)的分离
- 接口的本身反映了系统设计人员对系统的抽象理解
- 接口应有两类:
- 第一类是对一个个体的抽象,它可对应为一个抽象体(abstract class)
- 第二类是对一个个体某一方面的抽象,即形成一个抽象面(interface)
- 个体有可能有多个抽象面。抽象体与抽象面是有区别的
三个面向的过程
- 面向对象是指,我们考虑问题时,以对象为单位,考虑它的属性及方法
- 面向过程是指,我们考虑问题时,以一个具体的流程(事务过程)为单位,考虑它的实现
- 接口设计与非接口设计是针对复用技术而言的,与面向对象(过程)不是一个问题更多的体现就是对系统整体的架构
注解类型
mybatis最初配置信息是基于 XML,映射语句(SQL)也是定义在 XML 中的。而到MyBatis 3提供了新的基于注解的配置。不幸的是,Java 注解的的表达力和灵活性十分有限。最强大的 MyBatis 映射并不能用注解来构建(总的来说,注解不够灵活,推荐还是使用XML)
使用
直接在接口上面添加注解
//查询全部用户
@Select("select id,name,pwd password from user")
public List<User> getAllUser();
mybatis的核心配置文件中绑定接口
<!--使用class绑定接口-->
<mappers>
<mapper class="org.dam.dao.UserDao"/>
</mappers>
单元测试
@Test
public void testGetAllUser() {
SqlSession session = MybatisUtils.getSession();
//本质上利用了jvm的动态代理机制
UserMapper mapper = session.getMapper(UserMapper.class);
List<User> users = mapper.getAllUser();
for (User user : users){
System.out.println(user);
}
session.close();
}
增删改查
增
接口+注解
//添加一个用户
@Insert("insert into user (id,name,pwd) values (#{id},#{name},#{pwd})")
int addUser(User user);
单元测试
@Test
public void testAddUser() {
SqlSession session = MybatisUtils.getSession();
UserMapper mapper = session.getMapper(UserMapper.class);
User user = new User(6, "张三", "123456");
mapper.addUser(user);
session.close();
}
删
接口+注解
//根据id删除用
@Delete("delete from user where id = #{id}")
int deleteUser(@Param("id")int id);
单元测试
@Test
public void testDeleteUser() {
SqlSession session = MybatisUtils.getSession();
UserMapper mapper = session.getMapper(UserMapper.class);
mapper.deleteUser(6);
session.close();
}
改
接口+注解
//修改一个用户
@Update("update user set name=#{name},pwd=#{pwd} where id = #{id}")
int updateUser(User user);
单元测试
@Test
public void testUpdateUser() {
SqlSession session = MybatisUtils.getSession();
UserMapper mapper = session.getMapper(UserMapper.class);
User user = new User(6, "李四", "zxcvbn");
mapper.updateUser(user);
session.close();
}
查
接口+注解
//根据id查询用户
@Select("select * from user where id = #{id}")
User selectUserById(@Param("id") int id);
单元测试
@Test
public void testSelectUserById() {
SqlSession session = MybatisUtils.getSession();
UserMapper mapper = session.getMapper(UserMapper.class);
User user = mapper.selectUserById(1);
System.out.println(user);
session.close();
}
@Param
@Param注解用于给方法参数起一个名字。以下是总结的使用原则:
- 在方法只接受一个参数的情况下,可以不使用@Param
- 在方法接受多个参数的情况下,建议一定要使用@Param注解给参数命名
- 如果参数是 JavaBean , 则不能使用@Param
- 不使用@Param注解时,参数只能有一个,并且是JavaBean
#{}与${}的区别
#{}(推荐使用)
- 用来传入参数,sql在解析的时候会加上"",当成字符串来解析
- 主要是替换预编译语句 (PrepareStatement) 中的占位符?
- #{}能够很大程度上防止 sql 注入
INSERT INTO user (name) VALUES (#{name});
INSERT INTO user (name) VALUES (?);
${}
- 无法防止sql注入
- 直接进行字符串替换
INSERT INTO user (name) VALUES ('${name}');
INSERT INTO user (name) VALUES ('kuangshen');
复杂查询
开发准备
sql
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');
实体类
package org.dam.entity;
import lombok.Data;
/**
* @Author dam
* @create 2024/1/2 17:12
*/
@Data
public class Student {
private int id;
private String name;
//多个学生可以是同一个老师,即多对一
private Teacher teacher;
private int tid;
}
package org.dam.entity;
import lombok.Data;
import java.util.List;
/**
* @Author dam
* @create 2024/1/2 17:12
*/
@Data //GET,SET,ToString,有参,无参构造
public class Teacher {
private int id;
private String name;
//一个老师多个学生
private List<Student> students;
}
创建mapper
注册mapper到Mybatis的核心配置文件中
<!--每一个Mapper.xml都需要在MyBatis核心配置文件中注册-->
<mappers>
<mapper resource="org/dam/dao/xml/UserMapper.xml"/>
<mapper resource="org/dam/dao/xml/StudentMapper.xml"/>
<mapper resource="org/dam/dao/xml/TeacherMapper.xml"/>
</mappers>
也可以使用偷懒方式
<mappers>
<mapper resource="org/dam/dao/xml/*Mapper.xml"/>
</mappers>
多对一关系的查询
一个教师对应多个学生,查询每个学生的时候,都将其教师查询出来
准备工作
先给整个实体类的包取别名,后面使用实体类的时候方便,不需要写完整的引用
按查询嵌套处理
接口
package org.dam.dao;
import org.dam.entity.Student;
import java.util.List;
/**
* @Author dam
* @create 2024/1/2 17:13
*/
public interface StudentMapper {
//获取所有学生及对应老师的信息
public List<Student> getStudents();
}
mapper
需求
:
获取所有学生及其对应老师的信息
思路
:
- 先获取所有学生的信息
- 根据获取的学生信息的老师ID 来 获取对应老师的信息
- 学生的结果集中还需要包含老师,该如何处理呢?数据库中我们一般使用关联查询
- 做一个结果集映射:StudentTeacher
- StudentTeacher结果集的类型为 Student
- 学生中老师的属性为teacher,对应数据库中为tid(多个学生关联一个老师)
- association – 一个复杂类型的关联,使用它来处理关联查询
<?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="org.dam.dao.StudentMapper">
<select id="getStudents" resultMap="StudentTeacher">
select * from student
</select>
<resultMap id="StudentTeacher" type="student">
<association property="teacher" column="tid" javaType="teacher" select="getTeacher"/>
</resultMap>
<select id="getTeacher" resultType="teacher">
select * from teacher where id = #{id}
</select>
</mapper>
【解析】
这里传递过来的id,只有一个属性的时候,下面可以写任何值
<!--association关联属性
property属性名(Student里面的teacher字段用来存储教师的信息)
javaType属性类型(Teacher类名,因为前面加了配置给实体类包下的所有类取别名,所以这里直接写teacher即可)
column在多的一方的表中的列名(根据tid来查询教师信息)-->
<association property="teacher" column="tid" javaType="teacher" select="getTeacher"/>
<select id="getTeacher" resultType="teacher">
select * from teacher where id = #{id}
</select>
association中column多参数配置:column="{key=value,key=value}"
,其实就是键值对的形式,key是传给下个sql的取值名称,value是当前sql的字段名
<association column="{param1=id,param2=name}" property="User" select="getUser"></association>
<select id="getUser" resultMap="UserMap" parameterType="java.util.Map">
SELECT * FROM user_table WHERE id = #{param1} and name = #{param2}
</select>
单元测试
@Test
public void testGetStudents(){
SqlSession session = MybatisUtils.getSqlSession();
StudentMapper mapper = session.getMapper(StudentMapper.class);
List<Student> students = mapper.getStudents();
for (Student student : students){
System.out.println(
"学生名:"+ student.getName()
+"\t老师:"+student.getTeacher().getName());
}
}
输出
按结果嵌套处理
接口
public List<Student> getStudents2();
mapper
先直接从两个表里面直接查询出结果,然后进行结果集的映射
<select id="getStudents2" resultMap="StudentTeacher2">
select s.id sid, s.name sname, t.name tname
from student s,
teacher t
where s.tid = t.id
</select>
<resultMap id="StudentTeacher2" type="Student">
<id property="id" column="sid"/>
<result property="name" column="sname"/>
<!--关联对象property 关联对象在Student实体类中的属性-->
<association property="teacher" javaType="Teacher">
<result property="name" column="tname"/>
</association>
</resultMap>
单元测试
@Test
public void testGetStudents2(){
SqlSession session = MybatisUtils.getSqlSession();
StudentMapper mapper = session.getMapper(StudentMapper.class);
List<Student> students = mapper.getStudents2();
for (Student student : students){
System.out.println(
"学生名:"+ student.getName()
+"\t老师:"+student.getTeacher().getName());
}
}
小结
- 按照查询进行嵌套处理就像SQL中的子查询
- 按照结果进行嵌套处理就像SQL中的联表查询
一对多关系的查询
查询教师及其学生(使用集合来存储)
按结果嵌套处理
接口
package org.dam.dao;
import org.dam.entity.Teacher;
/**
* @Author dam
* @create 2024/1/2 17:13
*/
public interface TeacherMapper {
//获取指定老师,及老师下的所有学生
public Teacher getTeacher(int id);
}
mapper
- 从学生表和老师表中查出学生id,学生姓名,老师姓名 2. 对查询出来的操作做结果集映射,因为一个教师手下有多个学生,如果用集合存储的话,需要使用collection!
- JavaType和ofType都是用来指定对象类型的
- JavaType是用来指定pojo中属性的类型
- ofType指定的是映射到list集合属性中pojo的类型。
<?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="org.dam.dao.TeacherMapper">
<select id="getTeacher" resultMap="TeacherStudent">
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=#{id}
</select>
<resultMap id="TeacherStudent" type="teacher">
<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>
</mapper>
单元测试
@Test
public void testGetTeacher(){
SqlSession session = MybatisUtils.getSqlSession();
TeacherMapper mapper = session.getMapper(TeacherMapper.class);
Teacher teacher = mapper.getTeacher(1);
System.out.println(teacher.getName());
System.out.println(teacher.getStudents());
}
按查询嵌套处理
接口
public Teacher getTeacher2(int id);
mapper
<select id="getTeacher2" resultMap="TeacherStudent2">
select * from teacher where id = #{id}
</select>
<resultMap id="TeacherStudent2" type="teacher">
<!--column是一对多的外键 , 写的是一的主键的列名-->
<collection property="students" javaType="ArrayList" ofType="student" column="id" select="getStudentByTeacherId"/>
</resultMap>
<select id="getStudentByTeacherId" resultType="student">
select * from student where tid = #{id}
</select>
单元测试
@Test
public void testGetTeacher2(){
SqlSession session = MybatisUtils.getSqlSession();
TeacherMapper mapper = session.getMapper(TeacherMapper.class);
Teacher teacher = mapper.getTeacher2(1);
System.out.println(teacher.getName());
System.out.println(teacher.getStudents());
}
复杂查询总结
- 关联–association(用于一对一和多对一)
- 集合–collection(用于一对多)
JavaType和ofType都是用来指定对象类型的
- JavaType是用来指定pojo中属性的类型
- ofType指定的是映射到list集合属性中pojo的类型
动态SQL
MyBatis的强大特性之一便是它的动态SQL。如果你有使用JDBC或其它类似框架的经验,你就能体会到根据不同条件拼接SQL语句的痛苦。例如拼接时要确保不能忘记添加必要的空格,还要注意去掉列表最后一个列名的逗号。利用动态SQL这一特性可以彻底摆脱这种痛苦。虽然在以前使用动态SQL并非一件易事,但正是MyBatis提供了可以被用在任意SQL映射语句中的强大的动态SQL语言得以改进这种情形。动态SQL元素和JSTL或基于类似XML的文本处理器相似。在MyBatis之前的版本中,有很多元素需要花时间了解。MyBatis3大大精简了元素种类,现在只需学习原来一半的元素便可。MyBatis采用功能强大的基于OGNL的表达式来淘汰其它大部分元素。
- if
- choose(when,otherwise)
- trim(where,set)
- foreach
数据准备
sql
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
实体类
package org.dam.entity;
import lombok.Data;
import java.util.Date;
/**
* @Author dam
* @create 2024/1/3 10:57
*/
@Data
public class Blog {
private String id;
private String title;
private String author;
private Date createTime;
private int views;
}
修改Mybatis主要配置文件
注册mapper到配置文件中
接口
//新增一个博客
int addBlog(Blog blog);
mapper
<insert id="addBlog" parameterType="blog">
insert into blog (id, title, author, create_time, views)
values (#{id},#{title},#{author},#{createTime},#{views});
</insert>
工具类——ID生成
package org.dam.utils;
import java.util.UUID;
/**
* @Author dam
* @create 2024/1/3 10:56
*/
public class IDUtil {
public static String genId(){
return UUID.randomUUID().toString().replaceAll("-","");
}
}
单元测试
@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.commit();
session.close();
}
插入数据成功
if语句
接口
List<Blog> queryBlogIf(Map map);
mapper
如果作者名字为空,那么只根据博客名字模糊查询,反之,则根据博客名和作者名一起来查询
<select id="queryBlogIf" parameterType="map" resultType="blog">
select * from blog where
<if test="title != null">
title like #{title}
</if>
<if test="author != null">
and author = #{author}
</if>
</select>
单元测试
数据表的数据如下:
@Test
public void testQueryBlogIf(){
SqlSession session = MybatisUtils.getSqlSession();
BlogMapper mapper = session.getMapper(BlogMapper.class);
HashMap<String, String> map = new HashMap<String, String>();
map.put("title","%如此简单%");
List<Blog> blogs = mapper.queryBlogIf(map);
for (Blog blog : blogs) {
System.out.println(blog.toString());
}
session.close();
}
模糊查询出了四条数据
@Test
public void testQueryBlogIf(){
SqlSession session = MybatisUtils.getSqlSession();
BlogMapper mapper = session.getMapper(BlogMapper.class);
HashMap<String, String> map = new HashMap<String, String>();
map.put("title","%如此简单%");
map.put("author","狂神说");
List<Blog> blogs = mapper.queryBlogIf(map);
for (Blog blog : blogs) {
System.out.println(blog.toString());
}
session.close();
}
加入作者之后,只能查出两条数据
如果title为空呢?
@Test
public void testQueryBlogIf(){
SqlSession session = MybatisUtils.getSqlSession();
BlogMapper mapper = session.getMapper(BlogMapper.class);
HashMap<String, String> map = new HashMap<String, String>();
// map.put("title","%如此简单%");
map.put("author","狂神说");
List<Blog> blogs = mapper.queryBlogIf(map);
for (Blog blog : blogs) {
System.out.println(blog.toString());
}
session.close();
}
会出现sql报错,因为where后面直接就连接了and,出现这种问题可以使用下面的WHERE语句来处理
where语句
使用where标签包裹之后,如果查询条件是and或or开头的话,就会被自动剔除
mapper
<select id="queryBlogIf" parameterType="map" resultType="blog">
select * from blog
<where>
<if test="title != null">
title like #{title}
</if>
<if test="author != null">
and author = #{author}
</if>
</where>
</select>
单元测试
@Test
public void testQueryBlogIf() {
SqlSession session = MybatisUtils.getSqlSession();
BlogMapper mapper = session.getMapper(BlogMapper.class);
HashMap<String, String> map = new HashMap<String, String>();
// map.put("title","%如此简单%");
map.put("author", "狂神说");
List<Blog> blogs = mapper.queryBlogIf(map);
for (Blog blog : blogs) {
System.out.println(blog.toString());
}
session.close();
}
choose语句(类似switch)
接口
List<Blog> queryBlogChoose(Map map);
mapper
otherwise标签是可以省略的,按照自己需要的逻辑来写就行
<select id="queryBlogChoose" parameterType="map" resultType="blog">
select * from blog where
<choose>
<when test="title != null">
title like #{title}
</when>
<when test="author != null">
author = #{author}
</when>
<otherwise>
views > #{views}
</otherwise>
</choose>
</select>
单元测试
- 如果title不为null,根据title来查
@Test
public void testQueryBlogChoose() {
SqlSession session = MybatisUtils.getSqlSession();
BlogMapper mapper = session.getMapper(BlogMapper.class);
HashMap<String, String> map = new HashMap<String, String>();
map.put("title", "%如此简单%");
map.put("author", "狂神说");
map.put("views", "1");
List<Blog> blogs = mapper.queryBlogChoose(map);
for (Blog blog : blogs) {
System.out.println(blog.toString());
}
session.close();
- 如果author不为null,再根据author来查
@Test
public void testQueryBlogChoose() {
SqlSession session = MybatisUtils.getSqlSession();
BlogMapper mapper = session.getMapper(BlogMapper.class);
HashMap<String, String> map = new HashMap<String, String>();
map.put("author", "狂神说");
map.put("views", "1");
List<Blog> blogs = mapper.queryBlogChoose(map);
for (Blog blog : blogs) {
System.out.println(blog.toString());
}
session.close();
- 如果title和author都为null,根据views来查,只查询出阅读量超过1的博客
@Test
public void testQueryBlogChoose() {
SqlSession session = MybatisUtils.getSqlSession();
BlogMapper mapper = session.getMapper(BlogMapper.class);
HashMap<String, String> map = new HashMap<String, String>();
map.put("views", "1");
List<Blog> blogs = mapper.queryBlogChoose(map);
for (Blog blog : blogs) {
System.out.println(blog.toString());
}
session.close();
}
set语句
接口
int updateBlog(Map map);
mapper
<!--注意set是用的逗号隔开-->
<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 testUpdateBlog(){
SqlSession session = MybatisUtils.getSqlSession();
BlogMapper mapper = session.getMapper(BlogMapper.class);
HashMap<String, String> map = new HashMap<String, String>();
map.put("title","动态SQL");
map.put("author","老三");
map.put("id","4e59cf171b6249d98848af3d64ba1350");
mapper.updateBlog(map);
session.commit();
session.close();
}
假如author为空的话,set标签会将 title = #{title},
最后面的,剔除
@Test
public void testUpdateBlog(){
SqlSession session = MybatisUtils.getSqlSession();
BlogMapper mapper = session.getMapper(BlogMapper.class);
HashMap<String, String> map = new HashMap<String, String>();
map.put("title","动态SQL");
// map.put("author","老三");
map.put("id","4e59cf171b6249d98848af3d64ba1350");
mapper.updateBlog(map);
session.commit();
session.close();
}
sql片段封装
有时候某段sql可能需要被重复使用,为了简化代码,可以复用这段sql
将可复用的sql片段封装起来
<sql id="if-title-author">
<if test="title != null">
title = #{title}
</if>
<if test="author != null">
and author = #{author}
</if>
</sql>
使用sql片段
<select id="queryBlogIf" parameterType="map" resultType="blog">
select * from blog
<where>
<!-- 引用 sql 片段,如果refid 指定的不在本文件中,那么需要在前面加上 namespace -->
<include refid="if-title-author"></include>
<!-- 在这里还可以引用其他的 sql 片段 -->
</where>
</select>
注意:
- 最好基于单表来定义 sql 片段,提高片段的可重用性
- 在 sql 片段中不要包括 where
foreach语句(遍历)
动态SQL的另外一个常用的操作需求是对个集合进行遍历,通常是在构建in
条件语句的时候
foreach元素的功能非常强大,它允许你指定一个集合,声明可以在元素体内使用的集合项(item)和索引(index)变量。它也允许你指定开头与结尾的字符串以及在迭代结果之间放置分隔符。注意你可以将任何可迭代对象(如List、Set等)、Map对象或者数组对象传递给foreach作为集合参数。当使用可迭代对象或者数组时,index是当前迭代的次数,item的值是本次迭代获取的元素。当使用Map对象(或者Map.Entry对象的集合)时,index是键,item是值。
接口
List<Blog> queryBlogForeach(Map map);
mapper
【参数介绍】
- collection:指定输入对象中的集合属性
- item:每次遍历生成的对象
- open:开始遍历时的拼接字符串
- close:结束时拼接的字符串
- separator:遍历对象之间需要拼接的字符串
希望通过遍历,拼接出如下sql,select * from blog where 1=1 and (id=1 or id=2 or id=3)
<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 testQueryBlogForeach(){
SqlSession session = MybatisUtils.getSqlSession();
BlogMapper mapper = session.getMapper(BlogMapper.class);
HashMap map = new HashMap();
List<String> ids = new ArrayList<String>();
ids.add("4e59cf171b6249d98848af3d64ba1350");
ids.add("3767f2aded624958965e817019706b4c");
ids.add("ccf4063d396a4fb4a9fcfdb055e2de38");
map.put("ids",ids);
List<Blog> blogs = mapper.queryBlogForeach(map);
for (Blog blog : blogs) {
System.out.println(blog.toString());
}
session.close();
}
【运行结果】
[org.dam.dao.BlogMapper.queryBlogForeach]-==> Preparing: select * from blog WHERE ( id=? or id=? or id=? )
[org.dam.dao.BlogMapper.queryBlogForeach]-==> Parameters: 4e59cf171b6249d98848af3d64ba1350(String), 3767f2aded624958965e817019706b4c(String), ccf4063d396a4fb4a9fcfdb055e2de38(String)
[org.dam.dao.BlogMapper.queryBlogForeach]-<== Total: 3
Blog(id=4e59cf171b6249d98848af3d64ba1350, title=动态SQL, author=老三, createTime=Wed Jan 03 11:04:20 CST 2024, views=0)
Blog(id=3767f2aded624958965e817019706b4c, title=Spring如此简单, author=狂神说1, createTime=Wed Jan 03 11:04:20 CST 2024, views=0)
Blog(id=ccf4063d396a4fb4a9fcfdb055e2de38, title=微服务如此简单, author=狂神说1, createTime=Wed Jan 03 11:04:20 CST 2024, views=9999)
缓存
简介
- 什么是缓存[Cache]?
- 存在内存中的临时数据。
- 将用户经常查询的数据放在缓存(内存)中,用户去查询数据就不用从磁盘上(关系型数据库数据文件)查询,从缓存中查询,从而提高查询效率,解决了高并发系统的性能问题。
- 为什么使用缓存?
- 减少和数据库的交互次数,减少系统开销,提高系统效率。
- 什么样的数据能使用缓存?
- 经常查询并且不经常改变的数据。
Mybatis缓存介绍
MyBatis包含一个非常强大的查询缓存特性,它可以非常方便地定制和配置缓存。缓存可以极大的提升查询效率。
MyBatis系统中默认定义了两级缓存:一级缓存
和二级缓存
- 默认情况下,只有一级缓存开启。(SqlSession级别的缓存也称为本地缓存)
- 二级缓存需要手动开启和配置,他是基于namespace级别的缓存
- 为了提高扩展性,MyBatis定义了缓存接口Cache。我们可以通过实现Cache接口来自定义二级缓存
一级缓存(默认开启)
一级缓存也叫本地缓存:SqlSession
- 与数据库同一次会话期间查询到的数据会放在本地缓存中
- 以后如果需要获取相同的数据,直接从缓存中拿,没必须再去查询数据库
一级缓存相关特性
- 映射语句文件中的所有select语句的结果将会被缓存
- 映射语句文件中的所有insert、update和delete语句会刷新缓存
- 缓存会使用最近最少使用算法(LRU Least Recently Used)算法来清除不需要的缓存
- 缓存不会定时进行刷新(也就是说,没有刷新间隔)
- 缓存会保存列表或对象(无论查询方法返回哪种)的1024个引用
- 缓存会被视为读写缓存,这意味着获取到的对象并不是共享的,可以安全地被调用者修改,而不干扰其他调用者或线程所做的潜在修改
一级缓存测试
<select id="selectUserById" resultType="org.dam.entity.User">
select * from user where id = #{id}
</select>
@Test
public void testCache1() {
SqlSession session = MybatisUtils.getSqlSession();
UserDao mapper = session.getMapper(UserDao.class);
User user1 = mapper.selectUserById(1);
User user2 = mapper.selectUserById(1);
System.out.println(user1==user2);
session.close();
}
缓存失效情况
- 查询不同的东西
- 增删改操作,可能会改变原来的数据,所以必定会刷新缓存!
- 查询不同的Mapper.xml
- 手动清理了缓存(
sqlSession.clearCache()
)
总结
一级缓存默认是开启的,只在一次SqlSession中有效,也就是拿到连接到关闭连接这个区间段!一级缓存就是一个Map。
二级缓存
介绍
- 二级缓存也叫全局缓存,一级缓存作用域太低了,所以诞生了二级缓存
- 基于namespace级别的缓存,一个名称空间,对应一个二级缓存
- 工作机制
- 一个会话查询一条数据,这个数据就会被放在当前会话的一级缓存中
- 如果当前会话关闭了,这个会话对应的一级缓存就没了;但是我们想要的是,会话关闭了,一级缓存中的数据被保存到二级缓存中
- 新的会话查询信息,就可以从二级缓存中获取内容
- 不同的mapper查出的数据会放在自己对应的缓存(map)中
使用
在mybatis配置文件中开启全局缓存
<setting name="cacheEnabled" value="true"/>
去想要开启缓存的mapper.xml中配置开启二级缓存
简单配置
<cache/>
如果想要更精细化地定义缓存,可以使用类似下面的写法。表示:配置创建了一个 FIFO 缓存,每隔 60 秒刷新,最多可以存储结果对象或列表的 512 个引用,而且返回的对象被认为是只读的,因此对它们进行修改可能会在不同线程中的调用者产生冲突
<cache
eviction="FIFO"
flushInterval="60000"
size="512"
readOnly="true"/>
【参数解读】
- 可用的清除策略有:
LRU-最近最少使用(Mybatis默认)
:移除最长时间不被使用的对象FTFO-先进先出
:按对象进入缓存的顺序来移除它们S0FT-软引用
:基于垃圾回收器状态和软引用规则移除对象WEAK-弱引用
:更积极地基于垃圾收集器状态和弱引用规则移除对象
- flushlnterval(刷新间隔)属性可以被设置为任意的正整数,设置的值应该是一个以毫秒为单位的合理时间量。默认情况是不设置,也就是没有刷新间隔,缓存仅仅会在调用语句时刷新。
- size(引用数目)属性可以被设置为任意正整数,要注意欲缓存对象的大小和运行环境中可用的内存资源。默认值是1024。
- readOnly(只读)属性可以被设置为true或false。只读的缓存会给所有调用者返回缓存对象的相同实例。因此这些对象不能被修改。这就提供了可观的性能提升。而可读写的缓存会(通过序列化)返回缓存对象的拷贝。速度上会慢一些,但是更安全,因此默认值是fase。
提示
二级缓存是事务性的。这意味着,当SqlSession完成并提交时,或是完成并回滚,但没有执flushCache=true的insert/delete/update语句时,缓存会获得更新。
测试
@Test
public void testCache2(){
SqlSession session = MybatisUtils.getSqlSession();
SqlSession session2 = MybatisUtils.getSqlSession();
UserDao mapper = session.getMapper(UserDao.class);
UserDao mapper2 = session2.getMapper(UserDao.class);
User user = mapper.selectUserById(1);
System.out.println(user);
session.close();
User user2 = mapper2.selectUserById(1);
System.out.println(user2);
System.out.println(user==user2);
session2.close();
}
小结
- 只要开启了二级缓存,在同一个Mapper下就有效
- 所有的数据都会先放在一级缓存中
- 只有当会话提交,或者关闭的时候,才会提交到二级缓冲中
缓存的使用原理
建议
如果想要在项目更好地使用缓存,建议去学习Redis
文章说明
本文章为本人学习 遇见狂神说 的学习笔记,文章中大部分内容来源于狂神的视频(【狂神说Java】Mybatis最新完整教程IDEA版通俗易懂),也有部分内容来自于自己的思考,发布文章是想帮助其他学习的人更方便地整理自己的笔记或者直接通过文章学习相关知识,如有侵权请联系删除,最后对狂神的优质课程表示感谢。