mybatis的官方文档
一、第一个Mybatis程序
Mybatis-01
- 搭建环境
建立数据库,然后搭建一个maven项目。
导入mybatis相关的jar包
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.22</version>
</dependency>
- 获取sqlsession对象
先配置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>
<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=true&useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
<!-- 每一个Mapper.xml文件都需要在核心配置文件里面注册-->
<mappers>
<mapper resource="com/zyl/dao/userMapper.xml"/>
</mappers>
</configuration>
然后从 XML 中构建 SqlSessionFactory
从 SqlSessionFactory 中获取 SqlSession
package com.zyl.util;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.IOException;
import java.io.InputStream;
public class MybatisUtils {
private static SqlSessionFactory sqlSessionFactory;//提升作用域
static {
try {
//获取sqlSessionFactory对象,下面三句话是固定写法
String resource = "mybatis-config.xml";
InputStream inputStream =
Resources.getResourceAsStream(resource);
sqlSessionFactory = new
SqlSessionFactoryBuilder().build(inputStream);
} catch (IOException e) {
e.printStackTrace();
}
}
//获取SqlSession连接,类比之前的statement对象
//既然有了 SqlSessionFactory,顾名思义,我们可以从中获得 SqlSession 的实例。
// SqlSession 提供了在数据库执行 SQL 命令所需的所有方法。你可以通过 SqlSession 实例来直接执行已映射的 SQL 语句。
public static SqlSession getSession(){
return sqlSessionFactory.openSession();
}
}
- 编写代码
- 实体类
package com.zyl.pojo;
//实体类
public class User {
private int id; //id
private String name; //姓名
private String pwd; //密码
//有参构造、无参构造
//Getter、Setter 此处省略
}
- 接口类
这里不叫dao,交Mapper
package com.zyl.dao;
import com.zyl.pojo.User;
import java.util.List;
public interface UserMapper {
List<User> selectUser();
}
- 接口实现类
不叫impl了,叫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 绑定一个指定的Mapper接口-->
<mapper namespace="com.zyl.dao.UserMapper">
<!--select 查询语句
id:对应要实现的方法
resultType:返回值的类型
-->
<select id="selectUser" resultType="com.zyl.pojo.User">
select * from user
</select>
</mapper>
- 测试
用junit测试
package com.zyl.dao;
import com.zyl.pojo.User;
import com.zyl.util.MybatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
import java.util.List;
public class UserMapperTest {
public static void main(String[] args) {
}
@Test
public void userTest(){
//1. 获取sqlSession对象,通过工具类
SqlSession sqlSession=MybatisUtils.getSession();
try{
//2. 执行sql
UserMapper mapper=sqlSession.getMapper(UserMapper.class);
List<User> users=mapper.selectUser();
for (User user : users) {
System.out.println(user);
}
}catch (Exception e){
e.printStackTrace();
}finally {
sqlSession.close();
}
}
}
-
每一个Mapper.xml文件都需要在核心配置文件里面注册
-
还有就是maven约定大于配置的问题,配置文件可能无法生效或者导出,所以在pom.xml中加入下面的代码。
因为默认配置文件.xml文件是写在resource目录下的,现在Mapper.xml文件写在java目录下,没有生效。
<build>
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
</resources>
</build>
- 数据库连接的问题
因为本地数据库版本的问题,jdbc包应该改用下面的依赖
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.22</version>
</dependency>
还有就是核心配置文件中url的设置
<property name="url"
value="jdbc:mysql://localhost:3306/mybatis-study?useSSL=true&useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai"/>
二、CRUD
核心配置文件和工具类都不要在变。
只要写接口Mapper和Mapper.xml两个文件就行了。
我遇到一个问题,就是insert,update,delete写int返回值就报错,写void就可以运行。
问题解决:不是全是select,对应改成insert。。。
每一个Mapper.xml都要在核心配置文件里面注册
还有就是增删改要提交事务????
具体就是三步
- 编写接口
- 写Mapper.xml文件
- 写测试类(增删改要提交
事务
)
namespace
配置文件中namespace中的名称为对应Mapper接口或者Dao接口的完整包名,必须一致!
万能的Map
之前插入的话,传的参数是对象,要把对象的所有参数都写出来,麻烦。
字段或者参数过多,用map。(但是这玩意儿不正规)
接口
//万能的Map
int addUser2(Map<String,Object> map);
xml
<!-- Map
values里面传的是map中的键
-->
<insert id="addUser2" parameterType="map">
insert into user(id,name,pwd) values(#{userid},#{userName},#{password})
</insert>
测试类
@Test
public void addUser2(){
SqlSession sqlSession=MybatisUtils.getSession();
UserMapper mapper=sqlSession.getMapper(UserMapper.class);
Map<String,Object> map=new HashMap<String,Object>();
map.put("userid",9);
map.put("userName","zyl05");
map.put("password","123456");
int num=mapper.addUser2(map);
sqlSession.commit(); //真的要提交事务
sqlSession.close();
}
模糊查询like
接口
//模糊查询用户
List<User> getUserLike(String val);
- java代码执行时传通配符
xml
<!--模糊查询用户-->
<select id="getUserLike" resultType="com.zyl.pojo.User">
select * from user where name like #{val}
</select>
测试类
@Test
public void getUserLike(){
SqlSession sqlSession=MybatisUtils.getSession();
UserMapper mapper=sqlSession.getMapper(UserMapper.class);
List<User> userList=mapper.getUserLike("%李%");
for (User user : userList) {
System.out.println(user.getName());
}
sqlSession.close();
}
- sql拼接使用通配符
<!--模糊查询用户-->
<select id="getUserLike" resultType="com.zyl.pojo.User">
select * from user where name like "%"#{val}"%"
</select>
@Test
public void getUserLike(){
SqlSession sqlSession=MybatisUtils.getSession();
UserMapper mapper=sqlSession.getMapper(UserMapper.class);
List<User> userList=mapper.getUserLike("李");
for (User user : userList) {
System.out.println(user.getName());
}
sqlSession.close();
}
但是第二种方法容易被sql注入,不推荐使用。
三、配置解析
Mybatis-02
3.1 核心配置文件
mybatis-config.xml 系统核心配置文件
MyBatis 的配置文件包含了会深深影响 MyBatis 行为的设置和属性信息。
注意元素节点的顺序!顺序不对会报错
3.2 environments元素
-
配置MyBatis的多套运行环境,将SQL映射到多个不同的数据库上,必须指定其中一个为默认运行环境(通过default指定)
-
子元素 environment
具体的一套环境,通过设置id进行区别,id保证唯一!
子元素节点:transactionManager - [ 事务管理器 ]
子元素节点:数据源(dataSource)
3.3 properties属性
第一步 ; 在资源目录下新建一个db.properties
driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis-study?useSSL=true&useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
username=root
password=123456
第二步 : 将文件导入properties 配置文件
<configuration>
<!--导入properties文件,默认使用db.properties里面的信息-->
<properties resource="db.properties">
<property name="username" value="root"/>
<property name="password" value="111111"/>
</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>
注意<properties resource="db.properties"/>
这句话在核心配置文件中的位置是固定的,变动的话会出错。
可以直接引入外部配置文件
也可以在其中添加一些配置信息
如果两种方法有同一个字段,默认使用外部配置文件里面的字段。
3.4 typeAliases 别名
类型别名是为 Java 类型设置一个短的名字。它只和 XML 配置有关,存在的意义仅在于用来减少类完全限定名的冗余。
- 直接给实体类起别名
<typeAliases>
<!--直接给实体类起别名-->
<typeAlias type="com.kuang.pojo.User" alias="User"/>
</typeAliases>
- 扫描包,包下的实体类默认的别名就是实体类名首字母小写
<typeAliases>
<package name="com.zyl.pojo"/>
</typeAliases>
3.5 settings设置
懒加载,缓存开启关闭,日志
3.6 Mappers映射器
- 方式一:引入资源方式
<mappers>
<mapper resource="com/zyl/dao/userMapper.xml"/>
</mappers>
这个方法最好,随便怎么搞
- 方式二:使用class文件绑定注册
<mappers>
<mapper class="com.zyl.dao.UserMapper"/>
</mappers>
3. 方式三:使用扫描包的方式完成绑定
<mappers>
<!-- <mapper resource="com/zyl/dao/userMapper.xml"/>-->
<!-- <mapper class="com.zyl.dao.UserMapper"/>-->
<package name="com.zyl.dao"/>
</mappers>
他的问题和二一样。
还是推荐使用方式一
生命周期和作用域
- SqlSessionFactoryBuilder
- 他的作用在于创建 SqlSessionFactory,一旦创建就不再需要
- 局部变量
- SqlSessionFactory
-
可以被认为是一个数据库连接池,它的作用是创建 SqlSession 接口对象。
-
因为MyBatis 的本质就是 Java 对数据库的操作,所以 SqlSessionFactory 的生命周期存在于整个MyBatis 的应用之中,所以一旦创建了 SqlSessionFactory,就要长期保存它,直至不再使用MyBatis 应用,所以可以认为 SqlSessionFactory 的生命周期就等同于 MyBatis 的应用周期。
-
SqlSessionFactory 的最佳作用域是应用作用域。
-
用单例或静态单例实现
- SqlSession
-
相当于Statement
-
SqlSession实例线程不安全。所以 SqlSession 的最佳的作用域是请求或方法作用域。
-
用玩就关闭(finally),因为会占用资源。
四、ResultMap
Mybatis-03
解决属性名(实体类的属性)与字段名(数据库中表的字段)不一致的问题。
现在强行修改实体类的属性名。
package com.zyl.pojo;
//实体类
public class User {
private int id; //id
private String name; //姓名
private String password; //密码,原来是pwd,改成password
。。。
}
查询结果为null
数据库字段
实体类
接口
package com.zyl.dao;
import com.zyl.pojo.User;
import java.util.List;
import java.util.Map;
public interface UserMapper {
//根据id查询用户
User getUserById(int id);
}
映射文件
<select id="getUserById" parameterType="int" resultType="com.zyl.pojo.User">
select * from user where id=#{id}
</select>
测试
@Test
public void getUserById(){
SqlSession sqlSession=MybatisUtils.getSession();
try{
UserMapper mapper=sqlSession.getMapper(UserMapper.class);
User user=mapper.getUserById(1);
System.out.println(user.getPassword());
}catch (Exception e){
e.printStackTrace();
}finally {
sqlSession.close();
}
}
结果
分析
select * from user where id = #{id} 可以看做
select id,name,pwd from user where id = #{id}
mybatis会根据这些查询的列名(会将列名转化为小写,数据库不区分大小写) , 去对应的实体类中查找相应列名的set方法设值 , 由于找不到setPwd() , 所以password返回null ; 【自动映射】
ResultMap 结果集映射
UserMapper.xml进行修改
<!--结果集映射 type是使用的别名-->
<resultMap id="UserMap" type="user">
<!--column数据库中的字段 property实体类中的属性 映射关系-->
<result column="id" property="id"></result>
<result column="name" property="name"></result>
<result column="pwd" property="password"></result>
</resultMap>
<select id="getUserById" parameterType="int" resultMap="UserMap">
select * from user where id=#{id}
</select>
五、日志
5.1 日志工厂
数据库出现异常需要排错,使用日志比较好
SLF4J
LOG4J 【掌握】
LOG4J2
JDK_LOGGING
COMMONS_LOGGING
STDOUT_LOGGING 【掌握】
NO_LOGGING
在Mybatis中具体使用那一个日志输出,在设置settings中设定
配置是在核心配置文件里面配置,注意位置。
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
输出日志的结果
5.2 LOG4J
使用LOG4J和之前的标准日志工厂不一样,他需要导包。
- 导包
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
- 在resources文件夹里面写入配置文件
#将等级为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/kuang.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
- Setting设置日志实现
<settings>
<!--标准的日志工厂实现-->
<!--<setting name="logImpl" value="STDOUT_LOGGING"/>-->
<setting name="logImpl" value="LOG4J"/>
</settings>
- 测试
//日志对象,参数为当前了的class
//注意导包:org.apache.log4j.Logger
static Logger logger = Logger.getLogger(UserMapperTest.class);
@Test
public void log4jTest(){
logger.info("info:come into log4jTest");
logger.debug("debug:come into log4jTest");
logger.error("error:come into log4jTest");
}
5.3 limit实现分页
为什么需要分页
在学习mybatis等持久层框架的时候,会经常对数据进行增删改查操作,使用最多的是对数据库进行查询操作,如果查询大量数据的时候,我们往往使用分页进行查询,也就是每次处理小部分数据,这样对数据库压力就在可控范围内。
- 接口
//limit分页
List<User> getUserLimit(Map<String,Object> map);
- Mapper.xml
<select id="getUserLimit" parameterType="map" resultType="user">
select * from user limit #{startIndex},#{pageSize}
</select>
- 测试
@Test
public void getUserLimit(){
SqlSession sqlSession=MybatisUtils.getSession();
UserMapper mapper=sqlSession.getMapper(UserMapper.class);
Map<String,Object> map=new HashMap<String,Object>();
map.put("startIndex",0);
map.put("pageSize",5);
List<User> userList=mapper.getUserLimit(map);
for (User user : userList) {
System.out.println(user);
}
sqlSession.close();
}
5.4 RowBounds实现分页
5.5 PageHelper
六、使用注解开发
Mybatis-04
Mybatis的注解比较弱鸡,稍复杂的sql语句注解就力不从心了。
6.1 面向接口编程
- 大家之前都学过面向对象编程,也学习过接口,但在真正的开发中,很多时候我们会选择面向接口编程
- 根本原因 : 解耦 , 可拓展 , 提高复用 , 分层开发中 , 上层不用管具体的实现 , 大家都遵守共同的标准, 使得开发变得容易 , 规范性更好
定义和实现的分离。
6.2 使用注解开发
- 注解在接口上实现
public interface UserMapper {
//获取全部的用户
@Select("select * from user")
List<User> getUsers();
}
- 在核心配置文件绑定接口
<!--绑定接口,用class绑定-->
<mappers>
<mapper class="com.zyl.dao.UserMapper"/>
</mappers>
- 测试
@Test
public void test(){
SqlSession sqlSession= MybatisUtils.getSession();
UserMapper mapper=sqlSession.getMapper(UserMapper.class);
List<User> userList=mapper.getUsers();
for (User user : userList) {
System.out.println(user);
}
sqlSession.close();
}
6.3 注解原理
注解开发本质是反射。
底层是动态代理实现的。
可以打上断点来观察。
UserMapper mapper=sqlSession.getMapper(UserMapper.class);
这行代码拿到类的class对象,就可以获得类的所有信息,返回值,注解。。。
6.4 Mybatis执行流程(重要)没看完
6.5 注解实现crud
可以在工具类创建SqlSession实例时开启事务自动提交。
public interface UserMapper {
//获取全部的用户
@Select("select * from user")
List<User> getUsers();
//注意#{}里面取的值是@Param定义的值
@Select("select * from user where id=#{id2}")
User getUserById(@Param("id2") int id);
@Insert("insert into user(id,name,pwd) values(#{id},#{name},#{pwd})")
int addUser(User user);
@Update("update user set name=#{name},pwd=#{pwd} where id=#{id}")
int updateUser(User user);
@Delete("delete from user where id = #{id}")
int deleteUser(@Param("id")int id);
}
6.6 @Param &&#{}和${}区别
@Param注解用于给方法参数起一个名字。以下是总结的使用原则:
- 在方法只接受一个参数的情况下,可以不使用@Param。
- 在方法接受多个参数的情况下,建议一定要使用@Param注解给参数命名。
- 如果参数是 JavaBean , 则不能使用@Param。只能对基本类型使用。
- 不使用@Param注解时,参数只能有一个,并且是Javabean。
#{}和${}区别就好比PreparedStatement和Statement的区别,前者可以防止sql注入。
6.7 Lombok
使用步骤
- 先在idea上面下载lombok插件,安装完插件要重启idea
- 导入jar包
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
<scope>provided</scope>
</dependency>
- 注解说明
@Getter and @Setter
@FieldNameConstants
@ToString
@EqualsAndHashCode
@AllArgsConstructor, @RequiredArgsConstructor and @NoArgsConstructor
@Log, @Log4j, @Log4j2, @Slf4j, @XSlf4j, @CommonsLog, @JBossLog, @Flogger, @CustomLog
@Data
@Builder
@SuperBuilder
@Singular
@Delegate
@Value
@Accessors
@Wither
@With
@SneakyThrows
@val
@var
experimental @var
@UtilityClass
Lombok config system
Code inspections
Refactoring actions (lombok and delombok)
@Date
无参构造,get,set,hashcode,equals,toString
七、多对一的处理
Mybatis-05
能使用外键吗
多对一的理解:
多个学生对应一个老师
如果对于学生这边,就是多对一的现象,即从学生这边关联一个老师!
首先搭建环境
数据库环境
CREATE TABLE `teacher` (
`id` INT(10) NOT NULL,
`name` VARCHAR(30) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8
INSERT INTO teacher(`id`, `name`) VALUES (1, "秦老师");
CREATE TABLE `student` (
`id` INT(10) NOT NULL,
`name` VARCHAR(30) DEFAULT NULL,
`tid` INT(10) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `fktid` (`tid`),
CONSTRAINT `fktid` FOREIGN KEY (`tid`) REFERENCES `teacher` (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8
INSERT INTO `student` (`id`, `name`, `tid`) VALUES (1, "小明", 1);
INSERT INTO `student` (`id`, `name`, `tid`) VALUES (2, "小红", 1);
INSERT INTO `student` (`id`, `name`, `tid`) VALUES (3, "小张", 1);
INSERT INTO `student` (`id`, `name`, `tid`) VALUES (4, "小李", 1);
INSERT INTO `student` (`id`, `name`, `tid`) VALUES (5, "小王", 1);
基本项目环境搭建,添加实体类
public class Teacher {
private int id;
private String name;
}
public class Student {
private int id;
private String name;
//多个学生可以是同一个老师,即多对一
private Teacher teacher;
}
编写实体类对应的Mapper接口 【两个】
编写Mapper接口对应的 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">
<mapper namespace="com.zyl.dao.StudentMapper">
</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">
<mapper namespace="com.zyl.dao.StudentMapper">
</mapper>
按查询嵌套处理
- 给StudentMapper接口增加方法
//获取所有学生及对应老师的信息
public List<Student> getStudents();
- 编写对应的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">
<mapper namespace="com.zyl.dao.StudentMapper">
<!--
需求:获取所有学生及对应老师的信息
思路:
1. 获取所有学生的信息
2. 根据获取的学生信息的老师ID->获取该老师的信息
3. 思考问题,这样学生的结果集中应该包含老师,该如何处理呢,数据库中我们一般
使用关联查询?
1. 做一个结果集映射:StudentTeacher
2. StudentTeacher结果集的类型为 Student
3. 学生中老师的属性为teacher,对应数据库中为tid。
多个 [1,...)学生关联一个老师=> 一对一,一对多
4. 查看官网找到:association – 一个复杂类型的关联;使用它来处理关联查
询
-->
<select id="getStudents" resultMap="StudentTeacher">
select * from student
</select>
<resultMap id="StudentTeacher" type="Student">
<!--association关联属性 property属性名 javaType属性类型 column在多
的一方的表中的列名-->
<association property="teacher" column="tid" javaType="Teacher"
select="getTeacher"/>
</resultMap>
<!--
这里传递过来的id,只有一个属性的时候,下面可以写任何值
association中column多参数配置:
column="{key=value,key=value}"
其实就是键值对的形式,key是传给下个sql的取值名称,value是片段一中sql查询的
字段名。
-->
<select id="getTeacher" resultType="teacher">
select * from teacher where id = #{id}
</select>
</mapper>
- 编写完毕去Mybatis配置文件中,注册Mapper!
- 注意
<resultMap id="StudentTeacher" type="Student">
<!--association关联属性 property属性名 javaType属性类型 column在多的一方
的表中的列名-->
<association property="teacher" column="{id=tid,name=tid}"
javaType="Teacher" select="getTeacher"/>
</resultMap>
<!--
这里传递过来的id,只有一个属性的时候,下面可以写任何值
association中column多参数配置:
column="{key=value,key=value}"
其实就是键值对的形式,key是传给下个sql的取值名称,value是片段一中sql查询的字段
名。
-->
<select id="getTeacher" resultType="teacher">
select * from teacher where id = #{id} and name = #{name}
</select>
- 测试
@Test
public void testGetStudents(){
SqlSession session = MybatisUtils.getSession();
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文件
<!--
按查询结果嵌套处理
思路:
1. 直接查询出结果,进行结果集的映射
-->
<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>
-
去mybatis-config文件中注入【此处应该处理过了】
-
测试
@Test
public void testGetStudents2(){
SqlSession session = MybatisUtils.getSession();
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中的联表查询
八、一对多的处理
Mybatis-06
一对多的理解:
一个老师拥有多个学生
如果对于老师这边,就是一个一对多的现象,即从一个老师下面拥有一群学生(集合)!
实体类编写
public class Student {
private int id;
private String name;
private int tid;
}
public class Teacher {
private int id;
private String name;
//一个老师多个学生
private List<Student> students;
}
按结果嵌套处理
- TeacherMapper接口编写方法
//获取指定老师,及老师下的所有学生
public Teacher getTeacher(int id);
- 编写接口对应的Mapper配置文件
<!--
思路:
1. 从学生表和老师表中查出学生id,学生姓名,老师姓名
2. 对查询出来的操作做结果集映射
1. 集合的话,使用collection!
JavaType和ofType都是用来指定对象类型的
JavaType是用来指定pojo中属性的类型
ofType指定的是映射到list集合属性中pojo的类型。
-->
<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文件注册到MyBatis-config文件中
<mappers>
<mapper resource="com/zyl/dao/TeacherMapper.xml"/>
</mappers>
- 测试
@Test
public void testGetTeacher(){
SqlSession session = MybatisUtils.getSession();
TeacherMapper mapper = session.getMapper(TeacherMapper.class);
Teacher teacher = mapper.getTeacher(1);
System.out.println(teacher.getName());
System.out.println(teacher.getStudents());
}
- 出现的问题
资源过滤
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
</resources>
@Test
public void testGetTeacher(){
SqlSession session = MybatisUtils.getSession();
TeacherMapper mapper = session.getMapper(TeacherMapper.class);
Teacher teacher = mapper.getTeacher(1);
System.out.println(teacher.getName());
System.out.println(teacher.getStudents());
}
按查询嵌套处理
- TeacherMapper接口编写方法
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>
- 将Mapper文件注册到MyBatis-config文件中
- 测试
@Test
public void testGetTeacher2(){
SqlSession session = MybatisUtils.getSession();
TeacherMapper mapper = session.getMapper(TeacherMapper.class);
Teacher teacher = mapper.getTeacher2(1);
System.out.println(teacher.getName());
System.out.println(teacher.getStudents());
}
小结
九、动态sql
Mybatis-07
动态SQL指的是根据不同的查询条件 , 生成不同的Sql语句。但是可以在sql层面执行一些逻辑代码
我们之前写的 SQL 语句都比较简单,如果有比较复杂的业务,我们需要写复杂的 SQL 语句,往往需要拼接,而拼接 SQL ,稍微不注意,由于引号,空格等缺失可能都会导致错误。
那么怎么去解决这个问题呢?这就要使用 mybatis 动态SQL,通过 if, (choose, when, otherwise),trim, where, set, foreach等标签,可组合成非常灵活的SQL语句,从而在提高 SQL 语句的准确性的同时,也大大提高了开发人员的效率。
1.搭建环境(狂神笔记有,我就不写全了)
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
主要就是核心配置文件,实体类pojo,dao层的BlogMapper和他的xml文件。
然后写个测试类把数据插进去。
有个注意点:
我的Blog实体类的有一个属性和数据库对应的那个字段名不一致
public class Blog {
private String id;
private String title;
private String author;
private String createTime; //属性名和字段名不一致
private int views;
核心配置文件里面做如下添加,数据库_和java的驼峰命名自动匹配。
<settings>
<!--标准的日志工厂实现-->
<setting name="logImpl" value="STDOUT_LOGGING"/>
<!-- <setting name="logImpl" value="LOG4J"/>-->
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
1.where
where 元素只会在子元素返回任何内容的情况下才插入 “WHERE” 子句。而且,若子句的开头为 “AND” 或 “OR”,where 元素也会将它们去除。
2.set
上面的对于查询 SQL 语句包含 where 关键字,如果在进行更新操作的时候,含有 set 关键词,我们怎么处理呢?
set和where很像。
where根据具体参数删除and或者where
set也是根据具体的参数动态删除逗号。
- 编写接口方法
int updateBlog(Map map);
- sql
<!--注意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.getSession();
BlogMapper mapper = session.getMapper(BlogMapper.class);
HashMap<String, String> map = new HashMap<String, String>();
map.put("title","动态SQL");
map.put("author","秦疆");
map.put("id","9d6a763f5e1347cebda43e2a32687a77");
mapper.updateBlog(map);
session.close();
}
2.5 trim
trim可以定制化where和set
3. IF语句
需求:根据作者名字和博客名字来查询博客!如果作者名字为空,那么只根据博客名字查询,反之,则根据作者名来查询。
- 编写接口方法
//需求1
List<Blog> queryBlogIf(Map map);
- 编写sql语句
<!--需求1:
根据作者名字和博客名字来查询博客!
如果作者名字为空,那么只根据博客名字查询,反之,则根据作者名来查询
select * from blog where title = #{title} and author = #{author}
-->
<select id="queryBlogIf" parameterType="map" resultType="blog">
select * from blog where 1=1
<if test="title != null">
and title = #{title}
</if>
<if test="author != null">
and author = #{author}
</if>
</select>
上面的这种写法不行
修改成带where标签的。
如果只匹配author的话他会自动去掉and。
如果title和author一个都没匹配会自动去掉where。
<select id="queryBlogIf" parameterType="map" resultType="blog">
select * from blog
<where>
<if test="title != null">
title = #{title}
</if>
<if test="author != null">
and author = #{author}
</if>
</where>
</select>
- 测试
注意map里面可以不put title和author,什么都不put的话就是查询全部,put的话就是按put的条件查询。
@Test
public void queryBlogIf(){
SqlSession session = MybatisUtils.getSession();
BlogMapper mapper = session.getMapper(BlogMapper.class);
HashMap<String, String> map = new HashMap<String, String>();
//map.put("title","Mybatis如此简单");
//map.put("author","狂神说");
List<Blog> blogs = mapper.queryBlogIf(map);
System.out.println(blogs);
session.close();
}
4. choose语句
相当于switch-case
- 编写接口方法
//choose
List<Blog> queryBlogChoose(Map map);
- sql语句
<select id="queryBlogChoose" parameterType="map" resultType="blog">
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>
- 测试
测试就是把三个put注释看怎么走sql
@Test
public void testQueryBlogChoose(){
SqlSession session = MybatisUtils.getSession();
BlogMapper mapper = session.getMapper(BlogMapper.class);
HashMap<String, Object> map = new HashMap<String, Object>();
map.put("title","Java如此简单");
map.put("author","狂神说");
map.put("views",9999);
List<Blog> blogs = mapper.queryBlogChoose(map);
System.out.println(blogs);
session.close();
}
5. sql片段
有时候可能某个 sql 语句我们用的特别多,为了增加代码的重用性,简化代码,我们需要将这些代码抽取出来,然后使用时直接调用。
<sql id="if-title-author">
<if test="title != null">
title = #{title}
</if>
<if test="author != null">
and author = #{author}
</if>
</sql>
<select id="queryBlogIf" parameterType="map" resultType="blog">
select * from blog
<where>
<!-- 引用 sql 片段,如果refid 指定的不在本文件中,那么需要在前面加上 namespace-->
<include refid="if-title-author"></include>
<!-- 在这里还可以引用其他的 sql 片段 -->
</where>
</select>
6. foreach
动态 SQL 的另一个常见使用场景是对集合进行遍历(尤其是在构建 IN 条件语句的时候)。
十、缓存
Mybatis-08
简介
查询要连接数据库,比较耗费资源。
一次查询的结果可以放在内存了,要再次查询相同的数据的时候就直接在内存里面取。
只有读做缓存。
- 什么是缓存 [ Cache ]?
- 存在内存中的临时数据。
- 将用户经常查询的数据放在缓存(内存)中,用户去查询数据就不用从磁盘上(关系型数据库数据文件)查询,从缓存中查询,从而提高查询效率,解决了高并发系统的性能问题。
- 为什么使用缓存?
减少和数据库的交互次数,减少系统开销,提高系统效率。 - 什么样的数据能使用缓存?
经常查询并且不经常改变的数据。
Mybatis缓存
MyBatis包含一个非常强大的查询缓存特性,它可以非常方便地定制和配置缓存。缓存可以极大的提升查询效率。
MyBatis系统中默认定义了两级缓存:一级缓存和二级缓存
- 默认情况下,只有一级缓存开启。(SqlSession级别的缓存,也称为本地缓存)
- 二级缓存需要手动开启和配置,他是基于namespace级别的缓存。
- 为了提高扩展性,MyBatis定义了缓存接口Cache。我们可以通过实现Cache接口来自定义二级缓存
package org.apache.ibatis.cache;
import java.util.concurrent.locks.ReadWriteLock;
public interface Cache {
String getId();
void putObject(Object var1, Object var2);
Object getObject(Object var1);
Object removeObject(Object var1);
void clear();
int getSize();
default ReadWriteLock getReadWriteLock() {
return null;
}
}
一级缓存
一级缓存也叫本地缓存:
与数据库同一次会话期间查询到的数据会放在本地缓存中。放在sqlsession中。
以后如果需要获取相同的数据,直接从缓存中拿,没必须再去查询数据库;
一级缓存测试
- 在核心配置文件里面添加日志功能
<settings>
<!--标准的日志工厂实现-->
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
- 编写接口的方法
//根据id查询用户
User queryUserById(@Param("id") int id);
- 接口对应的xml文件
<select id="queryUserById" resultType="user">
select * from user where id = #{id}
</select>
- 测试
@Test
public void testQueryUserById(){
SqlSession session = MybatisUtils.getSession();
UserMapper mapper = session.getMapper(UserMapper.class);
User user = mapper.queryUserById(1);
System.out.println(user);
User user2 = mapper.queryUserById(1);
System.out.println(user2);
System.out.println(user==user2);
session.close();
}
- 结果分析
一级缓存失效
一级缓存失效有4种情况。
一级缓存是SqlSession级别的缓存,是一直开启的,我们关闭不了它;
一级缓存失效情况:没有使用到当前的一级缓存,效果就是,还需要再向数据库中发起一次查询请求!
1.SqlSession对象不同
@Test
public void testQueryUserById1(){
SqlSession session = MybatisUtils.getSession();
SqlSession session2 = MybatisUtils.getSession();
UserMapper mapper = session.getMapper(UserMapper.class);
UserMapper mapper2 = session2.getMapper(UserMapper.class);
User user = mapper.queryUserById(1);
System.out.println(user);
User user2 = mapper2.queryUserById(1);
System.out.println(user2);
System.out.println(user==user2);
session.close();
session2.close();
}
2.SqlSession对象相同,但是查询的数据不同
@Test
public void testQueryUserById(){
SqlSession session = MybatisUtils.getSession();
UserMapper mapper = session.getMapper(UserMapper.class);
UserMapper mapper2 = session.getMapper(UserMapper.class);
User user = mapper.queryUserById(1);
System.out.println(user);
User user2 = mapper2.queryUserById(2);
System.out.println(user2);
System.out.println(user==user2);
session.close();
}
3.sqlSession相同,两次查询之间执行了增删改操作!
@Test
public void testQueryUserById(){
SqlSession session = MybatisUtils.getSession();
UserMapper mapper = session.getMapper(UserMapper.class);
User user = mapper.queryUserById(1);
System.out.println(user);
HashMap map = new HashMap();
map.put("name","kuangshen");
map.put("id",4);
mapper.updateUser(map);
User user2 = mapper.queryUserById(1);
System.out.println(user2);
System.out.println(user==user2);
session.close();
}
4.sqlSession相同,手动清除一级缓存
@Test
public void testQueryUserById(){
SqlSession session = MybatisUtils.getSession();
UserMapper mapper = session.getMapper(UserMapper.class);
User user = mapper.queryUserById(1);
System.out.println(user);
session.clearCache();//手动清除缓存
User user2 = mapper.queryUserById(1);
System.out.println(user2);
System.out.println(user==user2);
session.close();
}
二级缓存
- 二级缓存也叫全局缓存,一级缓存作用域太低了,所以诞生了二级缓存
- 基于namespace级别的缓存,一个名称空间,对应一个二级缓存;
- 工作机制
- 一个会话查询一条数据,这个数据就会被放在当前会话的一级缓存中;
- 如果当前会话关闭了,这个会话对应的一级缓存就没了;但是我们想要的是,会话关闭了,一级缓存中的数据被保存到二级缓存中;
- 新的会话查询信息,就可以从二级缓存中获取内容;
- 不同的mapper查出的数据会放在自己对应的缓存(map)中;
使用步骤
- 开启全局缓存 【mybatis-config.xml】
<setting name="cacheEnabled" value="true"/>
- 去每个mapper.xml中配置使用二级缓存,这个配置非常简单;【xxxMapper.xml】
<cache/>
官方示例=====>查看官方文档
<cache
eviction="FIFO"
flushInterval="60000"
size="512"
readOnly="true"/>
这个更高级的配置创建了一个 FIFO 缓存,每隔 60 秒刷新,最多可以存储结果对象或列表的512 个引用,而且返回的对象被认为是只读的,因此对它们进行修改可能会在不同线程中的调用者产生冲突。
- 代码测试
所有的实体类先实现序列化接口
@Test
public void testQueryUserByIdTWO(){
SqlSession session = MybatisUtils.getSession();
SqlSession session2 = MybatisUtils.getSession();
UserMapper mapper = session.getMapper(UserMapper.class);
UserMapper mapper2 = session2.getMapper(UserMapper.class);
User user = mapper.queryUserById(1);
System.out.println(user);
session.close();
User user2 = mapper2.queryUserById(1);
System.out.println(user2);
System.out.println(user==user2);
session2.close();
}
缓存原理
先查二级缓存,找不到查一级缓存,找不到查数据库。
每一个mapper的二级缓存放一起。
一级缓存是放在每个SqlSession对象里面,已关闭一级缓存就没了,但是会把一级缓存里面的内容提交到对应mapper的二级缓存里面。
同时增删改会刷新缓存,但是也可以设置增删改不刷新缓存
自定义缓存EhCache
第三方缓存实现–EhCache: 查看百度百科
- Ehcache是一种广泛使用的java分布式缓存,用于通用缓存;
- 要在应用程序中使用Ehcache,需要引入依赖的jar包
<dependency>
<groupId>org.mybatis.caches</groupId>
<artifactId>mybatis-ehcache</artifactId>
<version>1.1.0</version>
</dependency>
- 在mapper.xml中使用对应的缓存即可
<mapper namespace = “org.acme.FooMapper” >
<cache type = “org.mybatis.caches.ehcache.EhcacheCache” />
</mapper>
- 编写ehcache.xml文件,如果在 加载时 未找到 /ehcache.xml 资源或出现问题,则将使用默认配置。
十一、总结
每一个实体类对应一个接口。