?传送门==>B站遇见狂神说–Mybatis教程
感谢狂神,学习视频真的是通俗易懂???
笔记和练习只是跟着视频整理的;有的知识点并没有整理进来
1.什么是 Mybatis
首先学习Mybatis; 可以使用到的参考Mybatis中文文档 https://mybatis.org/mybatis-3/zh/index.html MyBatis中文文档
MyBatis 持久层框架,支持自定义 SQL、存储过程以及高级映射。 MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。 MyBatis 可以通过简单的 XML 或注解 来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。
简单来说就是封装了一些方法;之前写项目的某些步骤可以省略.
历史由来
MyBatis 本是apache 的一个开源项目iBatis , 2010年这个项目由apache software foundation 迁移到了google code ,并且改名为MyBatis 。2013年11月迁移到Github 。
本人使用的版本情况 jdk — 1.8版本; MySql — 8.0.22 版本 Maven — 3.8.1 版本 Tomcat — 9.0.48 版本
1.1如何获取/下载Mybatis
(1)可以在GitHub下载 GitHub官网==>https://github.com/
搜索Mybatis即可
https://github.com/search?q=Mybatis
(2) 可以在Maven仓库下载 maven仓库官网:==>https://mvnrepository.com/
搜索Mybatis即可;
https://mvnrepository.com/search?q=Mybatis
一般选择使用人数较多的版本;比较稳定
我选择了3.4.6 版本的 Maven依赖如下所示
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.6</version>
</dependency>
1.2 什么是持久化,持久层
也就是数据持久化;将程序的数据在 持久状态和瞬时状态之间转化;
内存有个特点就是 断电即丢失 ;有的重要对象或者信息,如果丢失就很危险.
数据库,JDBC,IO 文件都有持久化.
持久层就是完成持久化工作的代码块;在项目中,层与层之间的界限是比较明显的.
1.3 为什么需要Mybatis
由于传统的JDBC优点复杂;那么简化它 ;Mybatis就出现了; 可以帮助程序员将数据存到数据库. 其实主要是懂原理,再使用这些框架就比较容易;
它比较简单 ;导入两个jar文件;配置几个sql映射文件就能上手了; sql写在xml里,便于统一管理
2. 入门Mybatis框架,先写个查询用户
2.1 首先是搭建环境
2.1.1先创建一个数据库;建个数据表,一会儿要对数据库进行操作;顺便存入几行数据
CREATE DATABASE IF NOT EXISTS `day2021_9_6_studyMybatis_db`;
CREATE TABLE `user`(
`id` INT(20) NOT NULL PRIMARY KEY,
`name` VARCHAR(10) DEFAULT NULL,
`password` VARCHAR(10) DEFAULT NULL
)ENGINE=INNODB DEFAULT CHARSET=utf8;
INSERT INTO `user`
VALUES
(1, '小智', '123456'),
(2, '杰哥', '111111'),
(3, '阿猫', '222222'),
(4, '点金手','666666'),
(5, '特工', '555555');
2.1.2 新建一个普通的maven项目;
注意项目名尽量不要用中文;之前有次我创建maven项目;用中文写项目名;进去爆红了…
记得在File 的setting设置 中检查maven的地址;有时候它会切到 IDEA 自带的maven地址; (好像之前改过默认地址了…,有时候还是会自动切换到IDEA的地址)
2.1.3 把创建的项目中的src目录删掉;那么这个项目就作为父级工程
2.1.4 导入项目需要的相关依赖;
在父级工程导入依赖;子级工程也能继承使用过去.
数据库连接驱动(我使用的是自己的8.0.22对应的版本 );mybatis持久层框架;测试使用的junit包
pom.xml文件
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.22</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.6</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
</dependencies>
2.2 创建一个模块,子级工程
在之前创建的项目处新建一个 Moudle 模块;仍然创建的是一个普通的maven项目;
那么,项目创建好了;作为子级项目,不用再去写依赖导包了,嘿嘿;
在pom.xml 文件还有显示它的父级
点击右边的maven;可以看到;子级项目study01 继承了刚才在Study-Mybatis01 中导入的包
2.2.1需要编写Mybatis的核心配置文件
在项目study01的src下的resources 目录下创建文件mybatis-config.xml文件 ;
注意在写url 的时候,后面设置的属性连接符号& 需要转义;&
<?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://127.0.0.1:3306/day2021_9_6_studyMybatis_db?useUnicode=true&characterEncoding=utf8&useSSL=true&serverTimezone=Asia/Shanghai"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="com/xiaozhi/dao/UserMapper.xml"/>
</mappers>
</configuration>
2.2.2 编写需要的工具类
每个基于 MyBatis 的应用都是以一个 SqlSessionFactory 的实例为核心的。 也可理解为工厂模式;
如果说每次操作都要去读取一下配置文件;是比较麻烦的,那么就写个工具类把要使用的的功能封装起来吧;每次操作去调用工具类;
在utils目录 下创建工具类MybatisUtils
好了,写工具类吧
package com.xiaozhi.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=null;
static {
try {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
} catch (IOException e) {
e.printStackTrace();
}
}
public static SqlSession getSqlSession(){
SqlSession sqlSession = sqlSessionFactory.openSession();
return sqlSession;
}
}
2.3 编写代码吧
2.3.1 首先需要创建实体类.
一般来说,将实体类存放在创建的pojo 目录或者eneity 目录中; 实体类的属性对应数据库的对应数据表的对应字段; 由于要和数据库对应,那么就注意属性的数据类型要一致;可不要把数值型的字段给写成字符串类型的或者别的一些错误. 基本上这些方法足够使用了;
package com.xiaozhi.eneity;
public class User {
private int id;
private String name;
private String password;
public User() {
}
public User(int id, String name, String password) {
this.id = id;
this.name = name;
this.password = password;
}
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 getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
", password='" + password + '\'' +
'}';
}
}
2.3.2创建Dao持久层;
这里用的接口对应配置文件的方式; 先用UserDao 命名吧;这个和之后要用的Mapper是一样的;
package com.xiaozhi.dao;
import com.xiaozhi.eneity.User;
import java.util.List;
public interface UserDao {
List<User> findAllUser();
}
需要注意的是,现在不用去写实现类去实现接口的方式,那样子比较麻烦; 比说说写个查询方法把;要获取结果集就得需要一个一个去写setXXX,getXXX对应属性;而且每次写一个方法,就得写对应的;
好了,接着回到这个简单的项目;创建个UserMapper.xml配置文件 ;
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.xiaozhi.dao.UserDao">
<select id="findAllUser" resultType="com.xiaozhi.eneity.User">
select * from day2021_9_6_studyMybatis_db.user
</select>
</mapper>
2.4 测试
使用junit测试;注意要使用@Test 注解.
一般情况下,这个测试代码是要在test测试类中去编写的;和开发的主程序分开;
package com.xiaozhi.dao;
import com.xiaozhi.eneity.User;
import com.xiaozhi.utils.MybatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
import java.util.List;
public class UserDaoTest {
@Test
public void testFindAllUser(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserDao userDao = sqlSession.getMapper(UserDao.class);
List<User> allUser = userDao.findAllUser();
for (User user : allUser) {
System.out.println(user);
}
sqlSession.close();
}
}
报错提示 (1)该错误表示没有在mybatis核心配置文件中注册需要的UserMapper.xml
org.apache.ibatis.binding.BindingException: Type interface com.xiaozhi.dao.UserDao is not known to the MapperRegistry.
(2)虽然说已经回到核心配置文件mybatis-config.xml ,把UserMapper.xml 添加注册进去了;
<!--每个mapper都需要在核心配置文件中注册-->
<mappers>
<mapper resource="com/xiaozhi/dao/UserMapper.xml"/>
</mappers>
但是又爆出找不到资源的错误;如下
java.lang.ExceptionInInitializerError
at com.xiaozhi.dao.UserDaoTest.testFindAllUser(UserDaoTest.java:18)
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.startRunnerWithArgs(IdeaTestRunner.java:33)
at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:220)
at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:53)
Caused by: org.apache.ibatis.exceptions.PersistenceException:
### Error building SqlSession.
### The error may exist in com/xiaozhi/dao/UserMapper.xml
### Cause: org.apache.ibatis.builder.BuilderException: Error parsing SQL Mapper Configuration. Cause: java.io.IOException: Could not find resource com/xiaozhi/dao/UserMapper.xml
.........................................
这段错误中提示找不到UserMapper.xml ;那么,想想之前学maven时,就学过遇到资源/配置文件找不到的情况时,要在项目的pom.xml配置文件 中写这样一段build ;
其实把UserMapper.xml配置文件复制粘贴到target目录下也行,但是每次资源找不到都这样做,比较麻烦,所以就直接在pom.xml文件中一次性搞定吧.
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>true</filtering>
</resource>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>true</filtering>
</resource>
</resources>
</build>
添加后,刷新一下maven;再次运行测试类UserDaoTest ;可发现结果已经查询出来了,且旁边的UserMapper.xml配置文件已加载到target目录下;
在测试类中,这个查询还有第二种方式,直接select定位到对应的持久层接口的方法中,但是可能需要强制转换类型为User, 返回结果;但是这种方式已经不常用了;
public class UserDaoTest {
@Test
public void testFindAllUser(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
List<User> list = sqlSession.selectList("com.xiaozhi.dao.UserDao.findAllUser");
for (User user : list) {
System.out.println(user);
}
sqlSession.close();
}
}
运行,也可以查询到;
3. 试试基础的增删改查(CRUD)
首先,看看刚才在mapper.xml配置文件 用到的select语句中的属性;
<mapper namespace="com.xiaozhi.dao.UserMapper">
<select id="findAllUser" resultType="com.xiaozhi.eneity.User">
select * from day2021_9_6_studyMybatis_db.user
</select>
</mapper>
要注意的是,在mapper.xml 文件中,namespace 的对应地址名要与持久层的接口名一致.
这里的id 对应namespace 中的方法名; resultType :表示SQL语句的返回值类型; 当然还有其他的属性; parameterType 表示参数类型,
3.1写个根据ID查询用户;
先在UserMapper 定义方法;
User findUserById(int id);
在UserMapper.xml 配置文件的mapper标签内写查询语句;注意parameterType 表示参数类型,查询语句中参数用#{ 字段}
<select id="findUserById" resultType="com.xiaozhi.eneity.User" parameterType="_int">
select * from day2021_9_6_studyMybatis_db.user where id= #{id}
</select>
在测试类中试试;
@Test
public void testFindUserById(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user = mapper.findUserById(1);
System.out.println(user);
sqlSession.close();
}
运行,查询得出结果
3.2 写个添加用户
先在UserMapper 定义方法;
int addUser(User user);
在UserMapper.xml 配置文件的mapper标签内写添加语句;
<insert id="addUser" parameterType="com.xiaozhi.eneity.User">
insert into day2021_9_6_studyMybatis_db.user(id,name,password)values (#{id},#{name},#{password});
</insert>
在测试类中试试;注意增删改需要提交事务哦
@Test
public void testAddUser(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
int i = mapper.addUser(new User(6, "阿虎", "145698"));
if(i>0){
System.out.println("添加数据条数=>"+i);
}
sqlSession.commit();
sqlSession.close();
}
3.3 写个修改用户
先在UserMapper 定义方法;
int updateUser(User user);
在UserMapper.xml 配置文件的mapper标签内写修改语句;
<update id="updateUser" parameterType="com.xiaozhi.eneity.User">
update day2021_9_6_studyMybatis_db.user set name=#{name},password=#{password} where id=#{id};
</update>
在测试类中试试;
@Test
public void testUpdateUser(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
int i = mapper.updateUser(new User(6, "虎子", "999999"));
if(i>0){
System.out.println("修改数据条数=>"+i);
}
sqlSession.commit();
sqlSession.close();
}
3.4 写个删除用户
先在UserMapper 定义方法;
int deleteUser(int id);
在UserMapper.xml 配置文件的mapper标签内写删除语句;
<update id="deleteUser" parameterType="_int">
delete from day2021_9_6_studyMybatis_db.user where id=#{id};
</update>
在测试类中测试;
@Test
public void testDeleteUser(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
int i = mapper.deleteUser(6);
if(i>0){
System.out.println("删除数据条数=>"+i);
}
sqlSession.commit();
sqlSession.close();
}
4. 使用Map
上面在写sql语句的时候,由于参数或返回值要对应实体类;那么每次sql语句都要准确地对应实体类的属性;
万能的Map来了,那么每次写SQL语句的时候,就可以直接参数写map的键;(而且使用map的好处就是,键不能重复) ; 然后再实际调用的时候,再对map的键进行赋值.
在UserMapper 中定义方法;
int allPowerfulMapToAdd(Map<String,Object> map);
在UserMapper.xml 配置文件的mapper标签内写添加语句;
<insert id="allPowerfulMapToAdd" parameterType="map">
insert into day2021_9_6_studyMybatis_db.user(id,name,password)values (#{useId},#{Name},#{password});
</insert>
测试一下;
@Test
public void testAddUserForMap(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
HashMap<String, Object> map = new HashMap<String, Object>();
map.put("useId",7);
map.put("Name","小鸡战士");
map.put("password","55555");
int i = mapper.allPowerfulMapToAdd(map);
if(i>0){
System.out.println("添加数据条数=>"+i);
}
sqlSession.commit();
sqlSession.close();
}
继续在UserMapper 中写个方法,准备去查询数据;
User allPowerfulMapToGet(Map<String,Object> map);
在UserMapper.xml 配置文件的mapper添加查询语句;
<select id="allPowerfulMapToGet" parameterType="map" resultType="com.xiaozhi.eneity.User">
select * from user where id= #{id} and name=#{name};
</select>
测试使用;
@Test
public void testSelUserForMap(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
HashMap<String, Object> map = new HashMap<String, Object>();
map.put("id",5);
map.put("name","特工");
User user = mapper.allPowerfulMapToGet(map);
System.out.println(user);
sqlSession.commit();
sqlSession.close();
}
5. 模糊查询
在UserMapper 中声明方法;
List<User> findUserByName(String name);
在UserMapper.xml 中添加文件;
<select id="findUserByName" resultType="com.xiaozhi.eneity.User" parameterType="String">
select * from user where name like #{name};
</select>
测试类调用方法;传参时使用%通配符 匹配;
@Test
public void testFindByName(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> userByName = mapper.findUserByName("%鱼%");
for (User user : userByName) {
System.out.println(user);
}
sqlSession.close();
}
还可以在sql拼接时 使用通配符 例如
<select id="findUserByName" resultType="com.xiaozhi.eneity.User" parameterType="String">
select * from user where name like "%"#{name}"%";
</select>
6.一些配置优化学习
查看下官方文档给出的配置文件说明.Mybatis中文文档在线
MyBatis 可以配置成适应多种环境,尽管可以配置多个环境,但每个 SqlSessionFactory 实例只能选择一种环境。
比如说要写个别的环境;想要使用它,就把environments 标签的 default 指向那个环境.
<environments default="test">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://127.0.0.1:3306/day2021_9_6_studyMybatis_db?useUnicode=true&characterEncoding=utf8&useSSL=true&serverTimezone=Asia/Shanghai"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
<environment id="test">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://127.0.0.1:3306/day2021_9_6_studyMybatis_db?useUnicode=true&characterEncoding=utf8&useSSL=true&serverTimezone=Asia/Shanghai"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
transactionManager :事务管理器 ; 有两种类型事务管理器([JDBC和MANAGED) ;默认使用JDBC; 这个MANAGED不常用;
dataSource 数据源 ;用于连接数据库.
有三种数据源;UNPOOLED 不使用数据库连接池;POOLED (默认) 使用数据库连接池;JNDI 应用服务器类使用;
6.1属性优化(properties)
还记着之前使用连接池时,读取properties文件的配置;还是比较方便的; 数据库连接池学习—DBCP;C3P0连接池
在resources 目录下编写db.properties 文件
driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://127.0.0.1:3306/day2021_9_6_studyMybatis_db?useUnicode=true&characterEncoding=utf8&useSSL=true&serverTimezone=Asia/Shanghai
username=root
password=123456
在核心配置文件mybatis-config.xml 配置文件中(引入)数据源; 注意properties 标签要写在核心配置文件的首位; 在环境标签下的dataSource下就能获取到了;就像之前那个el表达式的格式一样;${属性名} 就可读取到了;而且,由于配置文件是写在外部的,可以动态修改;
<?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>
<properties resource="db.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="com/xiaozhi/dao/UserMapper.xml"/>
</mappers>
</configuration>
properties 除了这种直接自闭合;
<properties resource="db.properties"/>
还可以在标签体添加属性;有时可能外部配置文件中的属性要被其他的文件读取;那么把这些属性写在外部配置文件就不合适;将这个文件需要的属性添加到properties 的标签体即可; 例如:
<properties resource="db.properties">
<property name="password" value="13245"/>
<property name="df" value="deqwq"/>
</properties>
需要注意的是:当外部配置文件和properties内定义的属性出现重名的情况;默认优先使用的是引入的外部文件内容!!!
6.2 别名优化(typeAliases)
使用别名可以减少代码的冗余,看起来也比较清晰明了
注意IDEA给出的提示,标签不能乱放位置
为实体类User配置别名
<typeAliases>
<typeAlias type="com.xiaozhi.eneity.User" alias="User"/>
</typeAliases>
在之前的UserMapper.xml 的查询语句返回值类型使用配置后的别名;测试,查询方法;可使用;
<select id="findAllUser" resultType="User">
select * from day2021_9_6_studyMybatis_db.user;
</select>
其实,在配置别名时,还可以使用扫描包的方式; 扫描存放实体类的包;别名默认为实体类的小写, 比如说配置扫描存放实例类User的包eneity;那么User类的别名就是user
<typeAliases>
<package name="com.xiaozhi.eneity"/>
</typeAliases>
在UserMapper.xml 的查询语句返回值类型使用别名user ;测试,查询成功;
<select id="findAllUser" resultType="user">
select * from day2021_9_6_studyMybatis_db.user;
</select>
在使用扫描包的方式;之后 ;如果不想用默认的小写开头的别名, 还可以直接在实体类使用注解的方式; 比如说在实体类User上面使用注解@Alias("别名") 注意导包为import org.apache.ibatis.type.Alias .
import org.apache.ibatis.type.Alias;
@Alias("myUser")
在UserMapper.xml 那个查询语句处使用注解写的别名;
<select id="findAllUser" resultType="myUser">
select * from day2021_9_6_studyMybatis_db.user;
</select>
需要注意的是,自带的一些别名,如果类型是基本类型的,就在原先的基础上前面加_ ;比如说,有个返回值为int 的;那么,你在配置类型的时候就给他用别名_int ;这是为了和他们的包装类区分;比如规定的Integer 类型的 别名 就是int .
其他类型的话,一般来说用小写就行;比如Date 别名就是date ;Float 别名就是float ;
6.3 设置(settings)
需要注意的几个;
mapUnderscoreToCamelCase
是否开启驼峰命名自动映射,即从经典数据库列名 A_COLUMN 映射到经典 Java 属性名 aColumn。 默认为false关闭状态的;
比如说在数据库写了个字段名为 res_time ;然后在java实体类里面用的是驼峰命名法resTime ;把这个属性一设置,它就能成功映射到对应的属性了;
logImpl ; 指定 MyBatis 所用日志的具体实现,未指定时将自动查找。
SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING
在下半部分会用到日志;
cacheEnabled :全局性地开启或关闭所有映射器配置文件中已配置的任何缓存。默认是开启的 ; lazyLoadingEnabled 懒加载的全局开关。开启后所有关联对象都会延迟加载。 特定关联关系中可通过设置 fetchType 属性来覆盖该项的开关状态。
6.4 映射器(mappers)
之前刚开始配好mybatis后,运行测试代码;出现的mapper映射问题;
org.apache.ibatis.binding.BindingException: Type interface com.xiaozhi.dao.UserDao is not known to the MapperRegistry.
MapperRegistry :注册绑定mapper文件;
需要在核心配置文件mybatis-config.xml ,把UserMapper.xml 添加注册;
主要有三种方式配置mapper (1)使用相对于类路径的资源引用;
<mappers>
<mapper resource="com/xiaozhi/dao/UserMapper.xml"/>
</mappers>
(2)使用映射器接口实现类的完全限定类名
<mappers>
<mapper class="com.xiaozhi.dao.UserMapper"/>
</mappers>
但使用这种方式有时可能会出错; 错误一:如果说把Mapper.xml配置文件放在了别的包下;会出现找不到mapper的错误; 错误二:例如说有时候不注意把接口命名为UserDao;而映射文件还是UserMapper.xml,这也会出现找不到mapper的情况. name怎么避免呢? ==> 保证接口和xml配置文件在同一个包下;且是同名的. (3)进行包扫描;将包下的所有映射器接口实现注册; 包扫描时也要保证接口和xml配置文件在同一个包下;且是同名的.
<mappers>
<package name="com.xiaozhi.dao.UserMapper"/>
</mappers>
7. 生命周期,作用域
生命周期与作用域使用不当会导致并发问题;
SqlSessionFactoryBuilder在 创建sqlsessionfactory后就不需要了;
SqlSessionFactory;可以理解为一个数据库连接池; 被创建就应在应用的运行期一直存在,不需要销毁或重新创建实例。适用于应用作用域,可使用单例模式或者静态单例模式.
SqlSession 是连接到数据库的一个请求,它的实例不是线程安全的,所以无法被共享,作用于请求或方法的作用域;用完就关闭,防止资源浪费. SqlSession可以去对应多个映射.
8. ResultMap 结果集映射
试试数据库字段名和实体类属性不一致的状况; 比如这里实体类的姓名username ;数据库为name
执行根据Id查询的方法;查询出的姓名为null,空值;(因为实体类的属性和数据库的字段名不一致了.)
解决方式一:在SQL查询语句使用别名;
<select id="findUserById" resultType="myUser" parameterType="_int">
select id,name as username,password from user where id= #{id};
</select>
解决方式二: 通过结果集映射
<resultMap id="UserMap" type="myUser">
<result property="id" column="id"/>
<result property="username" column="name"/>
<result property="password" column="password"/>
</resultMap>
<select id="findUserById" resultMap="UserMap" parameterType="_int">
select * from user where id= #{id};
</select>
9. 日志
在运行项目的时候,出现异常就需要依次排除错误,以前常用的System.out控制台输出或者debug调试显然是比较麻烦的;使用日志无疑是一个比较好的方式.
9.1使用标准日志工厂
注意,setting中设置日志时,name必须为logImpl ;
比如说,使用标准日志 STDOUT_LOGGING 在mybatis-config.xml 中设置开启日志;
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
测试执行根据Id查询的方法;
9.2 log4j 日志
可以控制打印的日志输出位置;控制台,文件或者GUI组件; 可控制输出格式;可定义日志的级别;可在配置文件中单独配置.
先把log4j的包导入到项目中; 在pom.xml 配置文件导入依赖;这里用的是log4j-1.2.17版本
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
在resources目录 下创建log4j.properties 资源文件;
#将等级为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/xiaozhi.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-config.xml 设置日志;
<settings>
<setting name="logImpl" value="LOG4J"/>
</settings>
测试运行;比之前的标准日志更详细;
使用 在需要使用log4j的类;导入包; import org.apache.log4j.Logger; 创建日志对象;参数为当前类的class;
static Logger logger= Logger.getLogger(UserDaoTest.class);
尝试在测试类中创建一个方法;调用日志方法;
static Logger logger= Logger.getLogger(UserDaoTest.class);
@Test
public void testForLog(){
logger.info("info:info信息==>");
logger.debug("debug:debug调试==>");
logger.error("error:异常==>");
}
查看控制台打印信息;
之前在lo4j.properties 文件中已经设置在文件目录生成日志信息;
10.分页
10.1 直接在SQL语句中用limit分页,然后传参.
在UserMapper 中定义方法;
List<User> findUserToLimit(Map<String ,Integer> map);
在UserMapper.xml 中写sql语句;
<select id="findUserToLimit" resultMap="UserMap" parameterType="map">
select * from user limit #{pageNum},#{pageSize};
</select>
测试使用方法;
@Test
public void testLimit(){
SqlSession sqlSession=MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
Map<String ,Integer> map=new HashMap<String ,Integer>();
map.put("pageNum",5);
map.put("pageSize",5);
List<User> userToLimit = mapper.findUserToLimit(map);
for (User user : userToLimit) {
System.out.println(user);
}
sqlSession.close();
}
10.2 使用 RowBounds分页(不推荐使用)
在UserMapper 定义方法;
List<User> findUserRowBounds();
在UserMapper.xml 中写sql语句,sql语句不写分页
<select id="findUserRowBounds" resultMap="UserMap">
select * from user
</select>
测试,
@Test
public void testRowBounds(){
SqlSession sqlSession=MybatisUtils.getSqlSession();
RowBounds rowBounds = new RowBounds(5,5);
List<User> selectList = sqlSession.selectList("com.xiaozhi.dao.UserMapper.findUserRowBounds",null,rowBounds);
for (User user : selectList) {
System.out.println(user);
}
sqlSession.close();
}
了解一下分页插件PageHelper
https://pagehelper.github.io/
11.使用注解开发
首先整个设置是面向接口编写程序的.
在UserMapper 接口的定义方法处直接使用注解;放入SQL语句;
可使用@Param注解,标明参数;sql传入的参数就是@Param注解中的参数名; 引用对象参数不用写@Param注解
package com.xiaozhi.dao;
import com.xiaozhi.pojo.User;
import org.apache.ibatis.annotations.Select;
import java.util.List;
public interface UserMapper {
@Select("select * from user where id=#{id} and name=#{name}")
User findUserById(@Param("id") int id,@Param("name") String name);
}
需要在mybatis-config.xml 中绑定接口;
<mappers>
<mapper class="com.xiaozhi.dao.UserMapper"/>
</mappers>
在测试时,其实已经通过反射得到接口的定义方法返回值,
@Test
public void testFindUserById(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user = mapper.findUserById(1, "小智");
System.out.println(user);
sqlSession.close();
}
Mybatis开发的流程
把之前写的工具类详细看看;
public class MybatisUtils {
private static SqlSessionFactory sqlSessionFactory=null;
static {
try {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
} catch (IOException e) {
e.printStackTrace();
}
}
public static SqlSession getSqlSession(){
SqlSession sqlSession = sqlSessionFactory.openSession();
return sqlSession;
}
}
首先是由Resources 获取且加载全局的配置文件,然后是实例化SqlSessionFactoryBuilder ; 点进build方法的源码; 里面是创建XMLConfigBuilder 来解析配置文件流的;
接着是Configuration 传入所有的配置文件信息;
实例化sqlSessionFactory ; 先通过事务管理transaction ; 然后创建executor执行器 ;
创建sqlSession ===>实现增删改查;如果出了问题,就需要事务回滚,回到事务管理;
若执行成功,提交事务;关闭资源.
在写工具类的时候,使用方法openSession() 来获取sqlSession;
进入源码查看;该方法有很多重载的;注意到有个以布尔值为参数的方法;查看它的实现;
SqlSession openSession(boolean var1);
是否开启自动提交事务;
public SqlSession openSession(boolean autoCommit) {
return this.openSessionFromDataSource(this.configuration.getDefaultExecutorType(), (TransactionIsolationLevel)null, autoCommit);
}
然后呢,在工具类获取sqlSession的时候,给openSession()传入参数true; ;也就是自动提交事务;那么就不用commit手动提交事务;
public static SqlSession getSqlSession(){
SqlSession sqlSession = sqlSessionFactory.openSession(true);
return sqlSession;
}
用注解完成一个添加方法; 在UserMapper 定义方法;且用注解完成sql语句;
@Insert("insert into user values(#{id},#{name},#{password})")
void addUser(User user);
在mybatis-config.xml 上面已经绑定过UserMapper了, 测试即可,无需手动提交事务;
@Test
public void testAddUser(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
mapper.addUser(new User(13,"蕉宝","846662"));
sqlSession.close();
}
扩展小知识,在写sql语句时,#{}可防止sql注入 , ${} 无法防止sql注入;
12. Lombok插件的使用
目前不建议使用
找到lombok的jar包;导入项目
https://mvnrepository.com/artifact/org.projectlombok/lombok
本人使用 1.18.20 版本.
在pom.xml 配置文件导入依赖,
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.20</version>
</dependency>
在idea安装插件 lombok ,插件的注解还是比较多的.
使用@Data 注解;生成无参构造,get,set方法,equals方法,hashCode方法,toString方法.
注解@AllArgsConstructor 生成有参构造方法
注解@NoArgsConstructor 生成无参构造方法.
注解@ToString 生成toString方法; 注解@EqualsAndHashCode 生成equals方法和hashCode方法
注解@Getter ,注解@Setter 放在类上,生成所有非静态的属性get,set方法; 放在属性上,仅生成该属性的get,set方法.
13. 复杂的查询
13.1 先搭建多个数据表的环境;
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);
INSERT INTO `student` (`id`, `name`, `tid`) VALUES (6, '臧绫', 1);
构建实体类;
@Data
public class Teacher {
private int id;
private String name;
}
@Data
public class Student {
private int id;
private String name;
private Teacher teacher;
}
mybatis-config.xml 配置文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<properties resource="db.properties"/>
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
<typeAliases>
<package name="com.xiaozhi.pojo"/>
</typeAliases>
<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 class="com.xiaozhi.dao.StudentMapper"/>
<mapper class="com.xiaozhi.dao.TeacherMapper"/>
</mappers>
</configuration>
13.2 多对一情况
比如说,想要查询出所有学生,且包括教师的信息; 先在StudentMapper 接口定义方法;
List<Student> finAllStudent();
然后去StudengMapper.xml 编写sql语句;
<select id="finAllStudent" resultType="student">
select * from student s ,teacher t where s.tid=t.id;
</select>
在测试目录下执行测试;
@Test
public void testFindStudent(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
List<Student> studentList = mapper.finAllStudent();
for (Student student : studentList) {
System.out.println(student);
}
sqlSession.close();
}
出现的问题就是查询到教师为null;空值;
按照类似子查询的方式, 查询嵌套
那么先把这个问题拆分开来; 用两段SQL语句; 先查询学生,再根据Id查询教师表;
在写sql之前;需要了解结果集Map映射 association 是表示对象; collection表示集合. javaType;表示指定属性的类型;一般来说,在集合中的泛型类型用ofType指定
定义的方法不变,在StudentMapper.xml配置文件 中修改SQL,且使用resultMap安排结果集映射,将教师实体类的属性作为集合,整体传进在学生实体类中的教师属性;
<select id="finAllStudent" resultMap="studentMap">
select * from student
</select>
<resultMap id="studentMap" type="student">
<result property="id" column="id"/>
<result property="name" column="name"/>
<association property="teacher" column="tid" javaType="teacher" select="findTeacherById"/>
</resultMap>
<select id="findTeacherById" resultType="teacher">
select * from teacher where id=#{tid};
</select>
执行测试;查询成功;
查询结果嵌套
在查询的结果集类型中将教师属性关联;
<select id="finAllStudent" resultMap="studentRMap2">
select s.id sid ,s.name sname , t.id teaid, t.name tname
from student s ,teacher t
where s.tid=t.id;
</select>
<resultMap id="studentRMap2" type="student">
<result property="id" column="sid"/>
<result property="name" column="sname"/>
<association property="teacher" javaType="teacher">
<result property="name" column="tname"/>
<result property="id" column="teaid"/>
</association>
</resultMap>
亦可查询
13.3 一对多的情况
一个教师对应查询多个学生
实体类需要做部分改动,学生类中不用封装教师类作为属性;在教师类中需要封装学生类集合;
学生类;
@Data
public class Student {
private int id;
private String name;
private int tid;
}
教师类
@Data
public class Teacher {
private int id;
private String name;
private List<Student> students;
}
按照查询语句嵌套
查询指定的教师对应的学生; 在TeacherMapper 接口中定义方法;
Teacher findStuByTeaId(@Param("tid") int id);
association 是表示对象; collection表示集合. javaType;表示指定属性的类型;一般来说,在集合中的泛型类型用ofType指定
在TeacherMapper.xml 配置文件中写入sql语句,以及它的对应结果集类型映射; 先根据Id查询教师,把查询的教师Id传入查询对应学生.
<select id="findStuByTeaId" resultMap="TeacherMap2">
select * from teacher where id=#{tid};
</select>
<resultMap id="TeacherMap2" type="teacher">
<result column="id" property="id"/>
<result column="name" property="name"/>
<collection property="students" javaType="ArrayList" ofType="student" column="id" select="findStuById"/>
</resultMap>
<select id="findStuById" resultType="student">
select * from student where tid=#{tid};
</select>
测试查询
@Test
public void testFindTeacher(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
TeacherMapper mapper = sqlSession.getMapper(TeacherMapper.class);
Teacher stuByTeaId = mapper.findStuByTeaId(1);
System.out.println(stuByTeaId);
sqlSession.close();
}
结果
按照结果嵌套查询
在TeacherMapper.xml 配置文件中写入sql语句,以及它的对应结果集类型映射;
<select id="findStuByTeaId" resultMap="TeacherMap">
select s.name sname , s.id sid ,t.id tid , t.name tname
from teacher t,student s where
t.id=s.tid and tid=#{tid}
</select>
<resultMap id="TeacherMap" 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>
测试查询结果;
14. 动态SQL的使用
根据不同的条件生成不同的sql语句
14.1首先搭建动态sql环境
首先创建个数据表;
CREATE TABLE `blog`(
id VARCHAR(200) NOT NULL COMMENT 'ID号',
`title` VARCHAR(50) 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;
去idea创建项目;导包,写mybatis-config.xml 配置文件;写工具类;和实体类;创建mapper层接口,以及mapper.xml配置文件.
实体类Blog
@Data
public class Blog {
private String id;
private String title;
private String author;
private Date createTime;
private int views;
}
做个生成随机Id的工具类; 使用UUID;
UUID 是 通用唯一识别码(Universally Unique Identifier)的缩写,是一种软件建构的标准,亦为开放软件基金会组织在分布式计算环境领域的一部分。
public class IdUtils {
public static String getId(){
return UUID.randomUUID().toString().replaceAll("-","");
}
}
对于刚才的实体类中属性createTime 和数据库名的字段create_time 命名不一致,可在mybatis的核心配置文件 中更改settings设置为开启驼峰命名自动转换.
mapUnderscoreToCamelCase 是否开启驼峰命名自动映射,即从经典数据库列名 A_COLUMN 映射到经典 Java 属性名 aColumn。
<?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>
<properties resource="db.properties"/>
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
<typeAliases>
<package name="com.xiaozhi.pojo"/>
</typeAliases>
<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 class="com.xiaozhi.dao.BlogMapper"/>
</mappers>
</configuration>
定义方法为数据表添加数据,顺便测试搭建环境; 在BlogMapper 接口定义方法
void addBlog(Blog blog);
在BlogMapper.xml 配置文件写sql语句;
<?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.xiaozhi.dao.BlogMapper">
<insert id="addBlog" parameterType="blog" >
insert into blog(id,title,author,create_time,views)
values (#{id},#{title},#{author},#{createTime},#{views})
</insert>
</mapper>
测试执行;
public class TestBlog {
@Test
public void test(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
Blog blog =new Blog();
blog.setId(IdUtils.getId());
blog.setTitle("学习JavaSE基础");
blog.setAuthor("JAVA大师");
blog.setCreateTime(new Date());
blog.setViews(10000);
mapper.addBlog(blog);
blog.setId(IdUtils.getId());
blog.setTitle("学习Mysql");
blog.setAuthor("CRUD专家");
blog.setViews(50000);
mapper.addBlog(blog);
blog.setId(IdUtils.getId());
blog.setTitle("学习框架");
blog.setAuthor("CV大法师");
blog.setViews(90000);
mapper.addBlog(blog);
blog.setId(IdUtils.getId());
blog.setTitle("学习多线程");
blog.setAuthor("杰斯");
blog.setViews(2000);
mapper.addBlog(blog);
sqlSession.close();
}
}
执行结果,成功;搭建的基本环境暂时没有问题;
14.2 动态SQL -->IF标签使用
在BlogMapper 接口定义查询方法
List<Blog> findBlogHaveIf(Map map);
在BlogMapper.xml中 编写sql语句; 注意会用到if判断;前面的 1=1 防止后面的条件若都不符合就查询输出所有的数据;
只要对需要查询的输入对应参数即可进行查询;不用去手动改变SQL语句;
<select id="findBlogHaveIf" resultType="blog" parameterType="map">
select * from blog where 1=1
<if test="title!=null">
and title =#{title}
</if>
<if test="author !=null">
and author =#{author}
</if>
<if test="views != null">
and views =#{views}
</if>
</select>
测试执行;
@Test
public void testFindBlogHaveIf(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
HashMap map=new HashMap();
List<Blog> blogs = mapper.findBlogHaveIf(map);
for (Blog blog : blogs) {
System.out.println(blog);
}
sqlSession.close();
}
若在执行时,对title属性进行赋值;就要查询这个指定的属性对应结果;只需在测试执行时添加;
@Test
public void testFindBlogHaveIf(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
HashMap map=new HashMap();
map.put("title","学习Mysql");
List<Blog> blogs = mapper.findBlogHaveIf(map);
for (Blog blog : blogs) {
System.out.println(blog);
}
sqlSession.close();
}
查询结果 ;它会自动地调整SQL语句.
若要添加title与views为过滤条件
@Test
public void testFindBlogHaveIf(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
HashMap map=new HashMap();
map.put("title","学习Mysql");
map.put("views","50000");
List<Blog> blogs = mapper.findBlogHaveIf(map);
for (Blog blog : blogs) {
System.out.println(blog);
}
sqlSession.close();
}
执行;
14.3 动态SQL的常用标签
where 标签;
在上面使用 if 标签时 ,注意到当时在 where 后拼接了一个 1=1;但是实际很多使用中 加这个1=1看起来不是很规范; 那么用<where> 标签替代where,且把需要执行的语句包裹起来,如果说where标签内没用到任何一个条件,那么where就不会拼接到SQL上去; 还有就是,如果where 后的条件开头有and 或者 or ,where标签都会把他们去掉.
动手试试吧.修改一下刚才的SQL语句;
<select id="findBlogHaveIf" resultType="blog" parameterType="map">
select * from blog
<where>
<if test="title!=null">
title =#{title}
</if>
<if test="author !=null">
and author =#{author}
</if>
<if test="views != null">
and views =#{views}
</if>
</where>
</select>
什么条件参数也不传入,看看是否会自动地不加where
@Test
public void testFindBlogHaveIf(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
HashMap map=new HashMap();
List<Blog> blogs = mapper.findBlogHaveIf(map);
for (Blog blog : blogs) {
System.out.println(blog);
}
sqlSession.close();
}
执行时,确实没有出现where
再通过条件浏览量 views 去查询;注意看看它是否会去掉前面的 and
@Test
public void testFindBlogHaveIf(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
HashMap map=new HashMap();
map.put("views","50000");
List<Blog> blogs = mapper.findBlogHaveIf(map);
for (Blog blog : blogs) {
System.out.println(blog);
}
sqlSession.close();
}
确实自动去掉了and
choose (when, otherwise)标签 若有多个条件,可选择其中的一个,类似于java的判断switch语句. otherwise 其他条件
还是查询方法;不过这次用choose进行判断过滤 在BlogMapper 接口定义方法;
List<Blog> findBlogByChoose(Map map);
在BlogMapper.xml中 编写sql语句;
<select id="findBlogByChoose" resultType="blog" parameterType="map">
select * from blog
<where>
<choose>
<when test="title != null">
title=#{title}
</when>
<when test="author !=null">
and author =#{author}
</when>
<otherwise>
and views =#{views}
</otherwise>
</choose>
</where>
</select>
输入title,author,views参数进行查询;
@Test
public void testFindBlogByChoose(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
HashMap map=new HashMap();
map.put("title","学习Mysql");
map.put("author","大师");
map.put("views","50000");
List<Blog> blogs = mapper.findBlogByChoose(map);
for (Blog blog : blogs) {
System.out.println(blog);
}
sqlSession.close();
}
执行时,可看到,仅选择了title 参数作为条件;
set set 标签可动态地在行首插入 SET 关键字,删掉额外的逗号
比如说写个修改方法; 在BlogMapper 接口中定义方法;
void updateBlogBySet(Map map);
在BlogMapper.xml 中编写sql语句;
<update id="updateBlogBySet" parameterType="map">
update blog
<set>
<if test="title !=null">
title= #{title},
</if>
<if test="author !=null">
author= #{author},
</if>
</set>
where id=#{id}
</update>
注意执行时,它自动去掉了 author 后的逗号
@Test
public void testUpdateBlogBySet(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
HashMap map=new HashMap();
map.put("title","关于转生那件事");
map.put("author","阿猫");
map.put("id","7de2fa32bb1f42649bbe3d04e2afd796");
mapper.updateBlogBySet(map);
sqlSession.close();
}
14.4 sql片段
SQL片段也就是把一些公用的SQL语句提取出来;方便使用;避免代码冗余.
可使用 <sql > 标签中写入公用的SQL语句;然后用<include> 标签去引用; <include> 标签 的refid 对应 <sql> 标签的id ;
建议不要将复杂SQL作为SQL片段.
比如说,在查询方法的SQL语句中,提取出SQL片段;用inclued标签引入.
<select id="findBlogHaveIf" resultType="blog" parameterType="map">
select * from blog
<where>
<include refid="isPublicCode"></include>
</where>
</select>
<sql id="isPublicCode">
<if test="title!=null">
title =#{title}
</if>
<if test="author !=null">
and author =#{author}
</if>
<if test="views != null">
and views =#{views}
</if>
</sql>
14.5 动态SQL之foreach标签.
遍历时,遍历的是一个集合,索引为index , 每次遍历取出的元素就是 item ; 用open 表示开头 ;close 表示结尾;使用separator 作为分隔符,将 SQL语句拼接起来.
练习;
先将数据表的ID修改一下;UUID太长了;
在BlogMapper 接口定义根据 id查询方法;
List<Blog> findBlogForEach(Map map);
在BlogMapper.xml 中编写SQL语句;注意在foreach标签中拼接SQL语句;
<select id="findBlogForEach" resultType="blog" parameterType="map">
select * from blog
<where>
<foreach collection="allId" item="id" open="and (" separator="or" close=")">
id=#{id}
</foreach>
</where>
</select>
测试执行;
@Test
public void testFindBlogForEach(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
HashMap map=new HashMap();
List<Integer> list=new ArrayList<Integer>();
list.add(1);
list.add(3);
map.put("allId",list);
List<Blog> blogForEach = mapper.findBlogForEach(map);
for (Blog blog : blogForEach) {
System.out.println(blog);
}
sqlSession.close();
}
注意到id的参数动态地拼接到where条件后
15.缓存
进行查询时,需要与数据库连接,会耗费资源,如果说可以把一次操作查询到的数据,先给他暂时放到一个地方,下次要用的时候直接去暂存的地方取,就不去数据库了;提高性能;也不耗费过多资源.
缓存就是在内存中的临时数据;可以把用户常用的查询数据放到缓存,可提高效率,解决高并发性能问题.
经常要查询 且不经常改变 的数据适合放入缓存.
15.1一级缓存
一级缓存是默认开启的 ,它是sqlSession级别 的.
与数据库同一次会话期间查询到的数据会放在本地缓存中。需要获取相同的数据,直接从缓存中拿,没必须再去查询数据库;
可以进行测试;先开启日志,然后将一个用户查询两次,看看是否来自同一个地址;
这次就用之前的用户表, 先在UserMapper 接口定义查询方法,
User findUserById(int id);
在UserMapper.xml 配置文件中编写sql
<select id="findUserById" resultType="user" parameterType="_int">
select * from user where id=#{id}
</select>
测试,再一次测试中根据Id查询同一个用户
public class TestUser {
@Test
public void testFindUserById(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user = mapper.findUserById(9);
System.out.println(user);
System.out.println("==============================");
User user1=mapper.findUserById(9);
System.out.println(user1);
System.out.println(user==user1);
sqlSession.close();
}
}
查看执行结果; 可看到sql语句与数据库仅交互了一次;且查询到的结果是来自同一个地址.
关于缓存失效
如果经过了添加,删除,或者更改,就刷新缓存.
试试看,在UserMapper 接口定义修改方法;
void updateUser(User user);
在UserMapper.xml 配置文件中编写sql
<update id="updateUser" parameterType="user">
update user set name=#{name},password=#{password} where id=#{id};
</update>
同样滴,这次虽然还是在一次测试中查询两次Id为9的用户;但是在第二次查询之前;执行一下修改语句;比如说修改Id 为4的 用户 ;注意观察,这次执行出的结果;两次查询 id为9 的用户,地址是否来自同一个.
public class TestUser {
@Test
public void testFindUserById(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user = mapper.findUserById(9);
System.out.println(user);
mapper.updateUser(new User(4,"修改","666666"));
System.out.println("==============================");
User user1=mapper.findUserById(9);
System.out.println(user1);
System.out.println(user==user1);
sqlSession.close();
}
}
注意查看执行日志,由于经过了修改语句,所以第一次查询到的结果缓存失效了 ;第二次又去重新进行了一次查询,最终两次得到的地址不是同一个位置.
查询不同的数据,上次的缓存也会失效,
查询不同的xml ;
手动清除缓存时,缓存失效;
试试手动清除缓存的方法;clearCache() 同样的,在第二次查询id为9的用户之前,手动清除缓存;
public class TestUser {
@Test
public void testFindUserById(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user = mapper.findUserById(9);
System.out.println(user);
sqlSession.clearCache();
System.out.println("==============================");
User user1=mapper.findUserById(9);
System.out.println(user1);
System.out.println(user==user1);
sqlSession.close();
}
}
执行;由于手动清除了缓存,第一次查询到的结果缓存被清除了;所以会在第二次查询时,又去与数据库交互.
15.2 二级缓存
在核心配置文件的settings设置中的属性 cacheEnabled 全局性地开启或关闭所有映射器配置文件中已配置的任何缓存。默认是开启的
二级缓存也就是全局缓存 , 它是namespace级别 的缓存,即一个名称空间就会对应一个二级缓存;
一级缓存是sqlSession级别的;若关闭sqlSession,一级缓存失效就没了; 在它关闭前要是把缓存转移到二级缓存;那么再开启一段新的sqlSession,二级缓存的数据还能用; 不同的mapper查出的数据会放在自己对应的缓存(map) 中
在Mapper.xml配置文件中 写入<cache/> 即开启了二级缓存.
当然,在<select> 标签处 可以使用 useCache 选择是否使用缓存,使用 flushCache 选择是否刷新缓存;
在<update>,<delete> ,<insert> 标签处 可使用 flushCache 选择是否刷新缓存.
在<cache中还可配置参数> 用参数eviction 选择清除策略;一下是几种清除策略
LRU –移除最长时间不被使用的对象。 FIFO – (先进先出):按对象进入缓存的顺序来移除。 SOFT – (软引用):基于垃圾回收器状态和软引用规则移除对象。 WEAK – 弱引用:更积极地基于垃圾收集器状态和弱引用规则移除对象。
用参数flushInterval 可设置缓存刷新时间,以毫秒为单位 ;
用参数以毫秒为单位 设置可存放的引用的个数,默认1024
用参数readOnly 设置是否只读,默认为false;只读的缓存会给所有调用者返回缓存对象的相同实例
试试练习; 首先不开二级缓存;执行查询方法; (还是之前那个查询方法,再写一次吧) UserMapper 接口定义的查询方法
User findUserById(int id);
还是UserMapper.xml中的语句
<select id="findUserById" resultType="user" parameterType="_int" >
select * from user where id=#{id}
</select>
这次的测试执行,首先开启两个 sqlSession ;也就是有两个mapper ;之前说过了不同的mapper查出的数据会放在自己对应的缓存(map) 中 ;那么试试看,在两个mapper中分别调用查询同一个用户的方法;
public class TestUser {
@Test
public void testFindUserById(){
SqlSession sqlSession1 = MybatisUtils.getSqlSession();
SqlSession sqlSession2 = MybatisUtils.getSqlSession();
UserMapper mapper1 = sqlSession1.getMapper(UserMapper.class);
UserMapper mapper2 = sqlSession1.getMapper(UserMapper.class);
User user1 = mapper1.findUserById(9);
System.out.println(user1);
System.out.println("==============================");
User user2 = mapper2.findUserById(9);
System.out.println(user2);
System.out.println("查询的两个用户是否来自同一地址=>"+(user1==user2));
sqlSession1.close();
sqlSession2.close();
}
}
执行结果;可看到,SQL语句执行了两次,两次结果不是同一地址.
那么;现在试试开启二级缓存后的效果; 在UserMapper.xml 配置文件中开启二级缓存.
<cache
eviction="FIFO"
flushInterval="60000"
size="512"
readOnly="true"/>
在第一个sqlSession关闭前,查询到的Id为9的用户数据被存到二级缓存中;
public class TestUser {
@Test
public void testFindUserById(){
SqlSession sqlSession1 = MybatisUtils.getSqlSession();
SqlSession sqlSession2 = MybatisUtils.getSqlSession();
UserMapper mapper1 = sqlSession1.getMapper(UserMapper.class);
UserMapper mapper2 = sqlSession2.getMapper(UserMapper.class);
User user1 = mapper1.findUserById(9);
System.out.println(user1);
sqlSession1.close();
System.out.println("==============================");
User user2 = mapper2.findUserById(9);
System.out.println(user2);
System.out.println("查询的两个用户是否来自同一地址=>"+(user1==user2));
sqlSession2.close();
}
}
执行;可看到sql语句仅执行了一次,且两次查询到的结果来自同一地址.
现在不给二级缓存设置任何参数 即在UserMapper.xml 配置文件中的二级缓存改为
<cache/>
再次执行刚才的测试程序;执行报错了.提示对象没有序列化.
Error serializing object. Cause: java.io.NotSerializableException: com.xiaozhi.pojo.User
好,那就去实体类实现Serializable 接口 ;给他开启序列化; 实体类User
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User implements Serializable {
private int id;
private String name;
private String password;
}
再次执行测试程序,sql语句执行了一次; 通过序列化返回缓存对象的拷贝,所以不是一个对象了
15.3 Mybatis缓存的原理
数据缓存的时候;先被存到一级缓存中,若开启了二级缓存;那么在sqlSession关闭之前就把数据存入二级缓存.
而进行访问查询的时候;第一次查询时,先看二级缓存中是否存在数据;然后才去二级缓存中查询;如果都没找到,去数据库查询;
15.4 自定义缓存 ehcache
Ehcache是一种广泛使用的开源Java分布式缓存。主要面向通用缓存,Java EE和轻量级容器。
首先要用它,去找jar包,话不多说,这就去Maven仓库查询;
<dependency>
<groupId>org.mybatis.caches</groupId>
<artifactId>mybatis-ehcache</artifactId>
<version>1.1.0</version>
</dependency>
在resources目录下创建 ehcache.xml配置文件
<?xml version="1.0" encoding="UTF-8" ?>
<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> 标签的type参数指定的类;
<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>
查看EhcacheCache 类
package org.mybatis.caches.ehcache;
public class EhcacheCache extends AbstractEhcacheCache {
public EhcacheCache(String id) {
super(id);
if (!CACHE_MANAGER.cacheExists(id)) {
CACHE_MANAGER.addCache(id);
}
this.cache = CACHE_MANAGER.getEhcache(id);
}
}
也可以自定义模仿写个缓存类;实现Cache 接口即可
|