Mybatis
简介:
MyBatis 是一款优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射。
MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。
MyBatis 可以使用简单的 XML 或注解来配置和映射原生信息,将接口和 Java 的 POJOs(Plain Old Java Objects,普通的 Java对象)映射成数据库中的记录
1.JDBC访问数据库的步骤:
1.加载数据库驱动 Driver
2.获取连接 Connection
3.获取sql执行器 Statement,PrepareStatement
4.执行sql,得到结果集 ResultSet
5.javaBean映射操作
6.关闭连接
内部组织结构:
核心原理:
2.核心对象:
SqlSessionFactoryBuilder
用来加载配置文件,调用build方法创建SqlSessionFactory对象
SqlSessionFactoryBuilder是通过xml配置文件或者configuration类的实例去构造这个对象。然后调用build()方法去构造SqlSessionFactory对象。
用过即丢,其生命周期只存在于方法体内
可重用其来创建多个SqlSessionFactory实例
负责构建SqlSessionFactory,并提供多个build方法的重载
SqlSessionFactory
SqlSessionFactory对象调用openSession方法获取SqlSession对象
SqlSessionFactory是我们MyBatis整个应用程序的中心;
整个程序都是以SqlSessionFactory的实例为核心的,
使用SqlSessionFactory对象的openSession()方法来获取SqlSession对象,
用SqlSession对象去操作数据库
因为SqlSession里面包含了以数据库为背景所有执行sql操作的方法。
SqlSession
包含了执行sql所需的所有方法
对应一次数据库会话,会话结束必须关闭
线程级别,不能共享在SqlSession里可以执行多次SQL语句,但一旦关闭了SqlSession就需要重新创建。
SqlSession实例不能被共享,也不是线程安全的。所以其最佳作用域是reqeust作用域内或者方法体内的作用域内。
3.Mybatis配置形式流程:
3.1配置依赖
<!--?xml version="1.0" encoding="UTF-8"?-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemalocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelversion>4.0.0</modelversion>
<groupid>com.iweb</groupid>
<artifactid>mybatis</artifactid>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupid>mysql</groupid>
<artifactid>mysql-connector-java</artifactid>
<version>5.1.39</version>
</dependency>
<dependency>
<groupid>c3p0</groupid>
<artifactid>c3p0</artifactid>
<version>0.9.1.2</version>
</dependency>
<!--mybatis依赖-->
<dependency>
<groupid>org.mybatis</groupid>
<artifactid>mybatis</artifactid>
<version>3.2.8</version>
</dependency>
<dependency>
<groupid>junit</groupid>
<artifactid>junit</artifactid>
<version>4.11</version>
</dependency>
</dependencies>
</project>
3.2创建实体类
package com.iweb.entity;
/**
**/
public class User {
private Integer id;
private String name;
private Integer age;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
'}';
}
}
3.3编写核心配置文件sqlMapConfig.xml
需要配置环境environment,
事务管理器transactionManager,
数据源POOLED,
引入映射文件,请看第五步
<!--?xml version="1.0" encoding="UTF-8" ?-->
<!--configuration核心配置标签-->
<configuration>
<!--environments环境配置-->
<environments default="mysql"><!--环境名任意取,要和下面的对应即可-->
<environment id="mysql">
<!--transactionManager事务管理器,type=JDBC使用jdbc管理事务-->
<transactionmanager type="JDBC">
<!-- 配置数据源 type="POOLED"使用mybatis自带的连接池-->
<datasource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver">
<property name="url" value="jdbc:mysql://localhost:3306/mybatis">
<property name="username" value="root">
<property name="password" value="123456">
</property></property></property></property></datasource>
</transactionmanager></environment>
</environments>
</configuration>
3.4编写映射文件,编写sql
这个配置文件写在resource目录中
namespace 其实就是实现对应的接口,和jdbc的接口实现类相似
select标签中的id,就相当于jdbc实现类的方法名;resultType 返回的javaBean类型
<!--?xml version="1.0" encoding="UTF-8" ?-->
<!--namespace命名空间,给SqlSession提供可以识别区分不同的命名空间
这个namespace 其实就是实现对应的接口,和jdbc的接口实现类相似
-->
<mapper namespace="com.iweb.mapper.UserMapper">
<!--id 唯一定义标识,在同一命名空间下,id不能重复
resultType 定义执行这条sql语句后,返回的javaBean类型
select标签 用来定义查询语句
-->
<select id="getAllUser" resulttype="com.iweb.entity.User">
select * from user
</select>
<!--parameterType 定义参数的类型-->
<select id="getUserById" parametertype="java.lang.Integer" resulttype="com.iweb.entity.User">
select * from user where id=#{id}
</select>
<!--当接收javaBean对象参数时,#{}在取值时,需和实体中的属性名相同,且要提供get、set方法-->
<insert id="addUser" parametertype="com.iweb.entity.User">
insert into user (username,password,nickname,email,phone,gender,status)
values (#{username},#{password},#{nickname},#{email},#{phone},#{gender},#{status})
</insert>
<insert id="addUser" parametertype="com.iweb.entity.User">
insert into user (username,password,nickname,email,phone,gender,status)
values (#{username},#{password},#{nickname},#{email},#{phone},#{gender},#{status})
</insert>
</mapper>
3.5将映射文件配置到sqlMapConfig.xml中
每一个Mapper.xml都需要在Mybatis核心配置文件中注册,需要用到mapper标签
<!--?xml version="1.0" encoding="UTF-8" ?-->
<!--configuration核心配置标签-->
<configuration>
<!--environments环境配置-->
<environments default="mysql"><!--环境名任意取,要和下面的对应即可-->
<environment id="mysql">
<!--transactionManager事务管理器,type=JDBC使用jdbc管理事务-->
<transactionmanager type="JDBC">
<!-- 配置数据源 type="POOLED"使用mybatis自带的连接池-->
<datasource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver">
<property name="url" value="jdbc:mysql://localhost:3306/mybatis">
<property name="username" value="root">
<property name="password" value="123456">
</property></property></property></property></datasource>
</transactionmanager></environment>
</environments>
<!--导入mapper.xml配置
配置映射文件
-->
<mappers>
<mapper resource="mapper/UserMapper.xml"></mapper>
<!--使用class属性指向需要使用注解的包路径或类路径-->
<mapper class="com.iweb.mapper.UserMapperAnnotation"></mapper>
</mappers>
</configuration>
3.6测试
1.获取mybatis主配置文件,得到一个输入流
2.创建SqlSessionFactoryBuilder对象
3.通过builder的build方法加载配置文件,创建SqlSessionFactory对象
4.获取SqlSession对象,支持事务,但不提交
factory.openSession(true);设置自动提交事务
测试类其实就三步:
1.获得一个sqlSession
2.获取mapper,拿到对应的mapper对象
3.通过mapper对象就可以调用接口中定义的方法
public void test1() throws Exception{
//1.获取mybatis主配置文件,得到一个输入流
InputStream in = Resources.getResourceAsStream("sqlMapConfig.xml");
//2.创建SqlSessionFactoryBuilder对象
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
//3.通过builder的build方法加载配置文件,创建SqlSessionFactory对象
SqlSessionFactory factory = builder.build(in);
//4.获取SqlSession对象,支持事务,但不提交
SqlSession session = factory.openSession();
//factory.openSession(true);//设置自动提交事务
// 里面执行sql语句的select标签id为getAllUser
List<object> list = session.selectList("test.getAllUser");
//更多使用自己配置的接口
UserMapper mapper = session.getMapper("UserMapper.xml");
User allUser = mapper.getAllUser;
//test.getAllUser:映射xml中的命名空间test
System.out.println(list);
}
4.Mybatis注解形式:
4.1创建核心配置文件 sqlMapConfig.xml
<!--?xml version="1.0" encoding="UTF-8" ?-->
<!--configuration核心配置标签-->
<configuration>
<!--environments环境配置-->
<environments default="mysql"><!--环境名任意取,要和下面的对应即可-->
<environment id="mysql">
<!--transactionManager事务管理器,type=JDBC使用jdbc管理事务-->
<transactionmanager type="JDBC">
<!-- 配置数据源 type="POOLED"使用mybatis自带的连接池-->
<datasource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver">
<property name="url" value="jdbc:mysql://localhost:3306/mybatis">
<property name="username" value="root">
<property name="password" value="123456">
</property></property></property></property></datasource>
</transactionmanager></environment>
</environments>
<!--导入mapper.xml配置
classpath:找到模块的resource路径
-->
<mappers>
<!--使用class属性指向需要使用注解的包路径或类路径-->
<mapper class="com.iweb.mapper.UserMapperAnnotation"></mapper>
</mappers>
</configuration>
4.2在mapper接口中使用注解
package com.iweb.mapper;
import com.iweb.entity.User;
import java.util.List;
import java.util.Map;
/**
* 注解形式mapper
*/
public interface UserMapperAnnotation {
@Select("select * from user")
public List<user> getAllUser();
@Select("select * from user where id=#{id}")
public User getUserById(Integer id);
@Delete("delete from user where id=#{id}")
public void deleteUser(Integer id);
}
4.3配置一个utils工具类,不用重复写获得sqlSession对象的代码
public static <t> T getAnnotationMapper(Class<t> clz){
InputStream in = null;
try{
//1.获取mybatis主配置文件,得到一个输入流
in = Resources.getResourceAsStream("mybatis-Config.xml");
//2.创建SqlSessionFactoryBuilder对象
// 通过builder的build方法加载配置文件,创建SqlSessionFactory对象
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
//3.获取SqlSession对象
SqlSession session = factory.openSession(true);
return session.getMapper(clz);
}catch (IOException e){
e.printStackTrace();
}finally {
try {
in.close();
}catch (IOException e){
e.printStackTrace();
}
}
return null;
}
自己写的工具类:
public class MybatisUtils {
//获取SqlSession
public static SqlSession getSqlSession() throws IOException {
//获取文件流
InputStream in = Resources.getResourceAsStream("sqlMapConfig.xml");
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory factory = builder.build(in);
SqlSession session = factory.openSession(true);
return session;
}
}
注解形式的测试:
@Test
public void test1(){
UserMapperAnnotation mapper = MybatisUtil.getAnnotationMapper(UserMapperAnnotation.class);
List<user> allUser = mapper.getAllUser();
System.out.println(allUser);
}
@Test
public void test2(){
UserMapperAnnotation mapper = MybatisUtil.getAnnotationMapper(UserMapperAnnotation.class);
System.out.println(mapper.getUserById(2));
}
@Test
public void test3(){
UserMapperAnnotation mapper = MybatisUtil.getAnnotationMapper(UserMapperAnnotation.class);
mapper.deleteUser(2);
}
5.sql中的取值方式:
#{}与${}
{}特点:
会作为sql参数使用,替代掉sql中的?
有预编译机制,防止sql注入,效率高
字符串类型,日期类型,会自动两边拼单引号
insert into user values(null,#{name},55) -->insert into user values(null,'张三',55)
insert into user values(null,${name},55) -->insert into user values(null,张三,55)
--sql语句错误
${}特点:
会原样直接拼接到sql上
没有预编译机制,不能防止sql注入,效率低
字符串类型,日期类型,不会自动两边拼单引号
select * from user order by #{name}--> select * from user order by 'name'//sql错误
select * from user order by ${name}--> select * from user order by name
总结:
通常都使用#{},可以防止sql注入,效率高,但如果传递的是列名,表名,关键字等必须直接拼接到sql的内容时,使用${},防止自动拼接单引号改变sql语义
6.参数的传值:
map传递参数,直接在sql中取出key即可 parameterType="map"
对象传递参数,直接在sq中取出对象的属性即可 parameterType="Object"
只有一个基本类型参数的情况下,可以直接在sql中取到
多个参数用map,或者注解
6.1单值传递
6.2map传值
当实体类或数据库中的表字段或参数过多时,考虑使用map传值。这样可以不用把所有的字段都写出来
比如说,数据库某表中有100个字段,当要修改数据库表中某一条记录的某一个字段时,使用map传值,就只需要写要改的字段即可。而如果是单值传递,就要把所有字段全部写上。
<insert id="updateUser" parametertype="map">
<!--此处values其实就是map中的键,也是表字段名-->
insert into user (id,password) values(#{id},#{password})
</insert>
map传递参数,直接在sql中取出key即可
@Test
public void test2() throws IOException {
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
Map<string, object=""> map = new HashMap<>();
//put的键对应xml中的values的键,为了代码可读性,还是要求名字相同
map.put("id","6");
map.put("password","tihuandemima");
mapper.updateUser(map);
}
模糊查询:
1.java代码执行的时候,传递通配符% %
List<user> userList = mapper.getUserList("%李%");
2.在xml中sql拼接中使用通配符
<select id="getUserList" resulttype="com.iweb.domain.User" select="" *="" from="" user="" where="" name="" like="" "%"#{name}"%"="" <="">
7.配置优化解析
7.1环境配置:
mybatis可以配置成适应多种环境
注意:尽管可以配置多个环境,但每个SqlSessionFactory实例只能选择一种环境
mybatis默认的事务管理器就是jdbc,连接池:POOLED
7.2属性:properties
通过properties属性来实现引用配置文件
这些属性都是可以通过外部配置动态替换的。
编写一个配置文件:db.properties
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=utf-8
username=root
password=123456
在核心配置文件中引入
properties标签必须写在最上面
<!--?xml version="1.0" encoding="UTF-8" ?-->
<!--configuration核心配置标签-->
<!--引入外部配置文件,properties放在resource目录下-->
<!--environments环境配置-->
<!--环境名任意取,要和下面的对应即可-->
<!--transactionManager事务管理器,type=JDBC使用jdbc管理事务-->
<!-- 配置数据源 type="POOLED"使用mybatis自带的连接池-->
7.3别名:typeAliases
放在configuration标签的第三个位置
类型别名是为java类型设置一个短名字
存在的意义仅在于减少类全名的冗余
3.1给实体类起别名
给实体类起别名之后就不用写全类名了,就写别名
</select>
select * from user
3.2也可以指定一个包名,mybatis会在包名下搜索需要的javaBean
扫描实体类的包,他默认的别名就是这个类的类名首字母小写
<typealiases>
<package name="com.iweb.domain">
</package></typealiases>
区别:
在实体类比较少的时候,使用第一种
当实体类特别多,建议使用第二种
第一种可以diy别名,第二种不行,如果非要改,需要在实体上增加注解@Alias
7.4映射器:mappers
每一个mapper.xml都需要在mybatis核心配置文件中注册
注册绑定我们的mapper文件
方式1:resource资源路径绑定 ——[推荐使用]
<mappers>
<mapper resource="com/iweb/mapper/UserMapper.xml">
</mapper></mappers>
方式2:使用class文件绑定注册
<mappers>
<mapper class="com.iweb.mapper.Usermapper">
</mapper></mappers>
注意:
接口和他的mapper配置文件必须同名
接口和他的mapper配置文件必须在同一个包下
方式3:使用扫描包进行注入绑定
<mappers>
<package name="com.iweb.mapper">
</package></mappers>
注意:
接口和他的mapper配置文件必须同名
接口和他的mapper配置文件必须在同一个包下
7.5生命周期和作用域
生命周期和作用域,是至关重要的,因为错误的使用会导致非常严重的并发问题
SqlSessionFactoryBuilder:
一旦创建了SqlSessionFactory,就不再需要它了
一般做成员变量
SqlSessionFactory:
说白了就是:数据库连接池
SqlSessionFactory一旦被创建,就会在应用的运行期间一直存在,没有任何理由丢弃或重新创建。因为丢弃和重新创建都会浪费内存资源
SqlSessionFactory的最佳作用域是application,也就是整个应用中,直到应用关闭
最简单的使用就是单例模式,保证全局只有一个
SqlSession:
连接到连接池的一个请求
SqlSession的实例不是线程安全的,因此不能被共享,所以它的最佳作用域是请求或方法作用域
用完之后需要赶紧关闭,否则资源被占用
一个SqlSessionFactory可以创建多个SqlSession,
每个SQLSession可以获得多个mapper,
这里的每一个mapper,就代表一个具体的业务
8.解决属性名和字段名不一致的问题:resultMap
实体类:
public class User {
private Integer id;
private String username;
private String pwd;//将这里的属性名和数据库字段名设置不一致
private String nickname;
}
mapper.xml
<select id="getUserList" resulttype="user">
select id,username,password from user
</select>
当获取信息后,由于属性名和字段名不一致,pwd的值会为空
解决方法:
方式1:起别名
<select id="getUserList" resulttype="user">
select id,username,password pwd from user
</select>
方式2:resultMap结果集映射
resultMap标签中
type:就是返回的数据类型
id:给select标签中的resultMap属性指定结果集映射
resultMap标签中的result标签
column:数据库表字段名;property:实体类属性名
<resultmap id="userMap" type="user">
<!--column:数据库中字段名。property:实体类中属性名-->
<result column="password" property="pwd">
</result></resultmap>
<select id="getUserList" resultmap="userMap">
select * from user where id=#{id}
</select>
9.日志工厂:
主要用来排错,把错误的信息用日志记录
在mybatis核心配置文件中配置settings
<settings>
<!--标准日志工厂实现-->
<setting name="logImpl" value="STDOUT_LOGGING">
</setting></settings>
log4j:
可以将日志信息传送到控制台、文件、GUI组件中
可以控制每一条日志的输出格式,通过一个配置文件来灵活进行配置(log4j.properties)
1.导入log4j依赖
<dependency>
<groupid>log4j</groupid>
<artifactid>log4j</artifactid>
<version>1.2.17</version>
</dependency>
2.log4j.properties(根据具体输出到哪里而定,网上可搜)
#############
# 输出到控制台
#############
# log4j.rootLogger日志输出类别和级别:只输出不低于该级别的日志信息DEBUG < INFO < WARN < ERROR < FATAL
# WARN:日志级别 CONSOLE:输出位置自己定义的一个名字 logfile:输出位置自己定义的一个名字
log4j.rootLogger=WARN,CONSOLE,logfile
# 配置CONSOLE输出到控制台
log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
# 配置CONSOLE设置为自定义布局模式
log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
# 配置CONSOLE日志的输出格式 [frame] 2019-08-22 22:52:12,000 %r耗费毫秒数 %p日志的优先级 %t线程名 %C所属类名通常为全类名 %L代码中的行号 %x线程相关联的NDC %m日志 %n换行
log4j.appender.CONSOLE.layout.ConversionPattern=[frame] %d{yyyy-MM-dd HH:mm:ss,SSS} - %-4r %-5p [%t] %C:%L %x - %m%n
################
# 输出到日志文件中
################
# 配置logfile输出到文件中 文件大小到达指定尺寸的时候产生新的日志文件
log4j.appender.logfile=org.apache.log4j.RollingFileAppender
# 保存编码格式
log4j.appender.logfile.Encoding=UTF-8
# 输出文件位置此为项目根目录下的logs文件夹中
log4j.appender.logfile.File=logs/root.log
# 后缀可以是KB,MB,GB达到该大小后创建新的日志文件
log4j.appender.logfile.MaxFileSize=10MB
# 设置滚定文件的最大值3 指可以产生root.log.1、root.log.2、root.log.3和root.log四个日志文件
log4j.appender.logfile.MaxBackupIndex=3
# 配置logfile为自定义布局模式
log4j.appender.logfile.layout=org.apache.log4j.PatternLayout
log4j.appender.logfile.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %F %p %m%n
##########################
# 对不同的类输出不同的日志文件
##########################
# club.bagedate包下的日志单独输出
log4j.logger.club.bagedate=DEBUG,bagedate
# 设置为false该日志信息就不会加入到rootLogger中了
log4j.additivity.club.bagedate=false
# 下面就和上面配置一样了
log4j.appender.bagedate=org.apache.log4j.RollingFileAppender
log4j.appender.bagedate.Encoding=UTF-8
log4j.appender.bagedate.File=logs/bagedate.log
log4j.appender.bagedate.MaxFileSize=10MB
log4j.appender.bagedate.MaxBackupIndex=3
log4j.appender.bagedate.layout=org.apache.log4j.PatternLayout
log4j.appender.bagedate.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %F %p %m%n
3.在核心配置文件中配置log4j日志的实现
<settings>
<!--log4j日志工厂实现-->
<setting name="logImpl" value="LOG4J">
</setting></settings>
简单使用:
1.在要使用log4j的类中,导入import.org.apache.log4j.Logger;
2.创建日志对象,参数为当前类的class
static Logger logger = Logger.getLogger(xxx.class);
3.日志级别
logger.info("info:");
logger.debug("debug:");
logger.error("error:");
10.分页
10.1使用limit分页
语法:select * from xxx表 limit startIndex,pageSize;
select * from user limit 3;//显示前三条
使用mybatis实现分页,核心sql
1.接口
List<user> getUserByLimit(Map<string,integer> map);
2.Mapper.xml
<select id="getUserByLimit" parametertype="map" resultmap="UserMap">
select * from user limit #{startIndex},#{pageSize}
</select>
3.测试
@Test
public void getUserByLimit() throws IOException {
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
HashMap<string, integer=""> map = new HashMap<string, integer="">();
map.put("startIndex",1);
map.put("pageSize",2);
List<user> userList = mapper.getUserByLimit(map);
for (User user : userList) {
System.out.println(user);
}
sqlSession.close();
}
10.2使用分页插件
pageHelper
11.使用注解开发
11.1基本流程
1.注解在接口上实现(增删改查)
@Select("select * from user")
List<user> getUsers();
2.需要在核心配置文件中绑定接口
<mappers>
<mapper class="com.iweb.domain.UserMapper">
</mapper></mappers>
本质:反射机制实现
底层流程:
1.Resources获取加载全局配置文件
//例如:全局配置文件mybatis-config.xml
InputStream in = Resource.getResourceAsStream("mybatis-config.xml");
2.实例化SqlSessionFactoryBuilder
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
3.解析配置文件流XMLConfigBuilder(SqlSessionFactoryBuilder源码中)
build方法做的事情(3,4步骤)
4.所有的配置信息放在Configuration对象里
5.SqlSessionFactory实例化
(build完之后产生SqlSessionFactory对象)
sqlSessionFactory = new SqlSessionFactoryBuilder().build(in);
6.transaction事务管理
7.创建executor执行器
执行mapper
8.创建sqlSession对象
实现CRUD操作,可能会出现sql问题,事务会回滚,回到步骤6
9.执行成功,提交事务
10.关闭sqlSession
11.2CRUD
1.当方法存在多个参数,所有的参数前必须加上@Param("字段名")这个注解
编写接口,添加注解
@Select("select * from user where id = #{id} and name = #{张三}")
//此时取的参数对应的是注解Param中的参数名,必须和字段名一致
User getUser(@Param("id") int id,@Param("name") String name);
@Select、@Insert、@Update、@Delete
注意:必须要将接口注册绑定到核心配置文件中
关于@Param()注解
基本类型或String类型参数,需要加上,引用类型不需要
如果只有一个基本类型,可以忽略,但建议都加上
在sql中引用的就是这里的@Param("xxx")中设定的属性名,所有要求字段名和属性名相对应
12.多对一处理
多个学生,对应一个老师
对于学生来说,多个学生,(association)关联一个老师【多对一】
对应老师来说,一个老师,(collection)有很多学生【一对多】
环境搭建流程:
1.导入lombok,建立实体类
2.建立对应实体类的Mapper接口
3.建立对应Mapper.xml文件
4.在核心配置文件中绑定注册Mapper接口或文件
5.测试CRUD是否能成功
学生实体类:
@Data
public class Student{
private int id;
private String name;
//学生需要关联一个老师
private Teacher teacher;
}
老师实体类:
@Data
public class Teacher{
private int id;
private String name;
}
查询嵌套处理:
比如,有一个学生表,一个老师表,学生和老师通过tid和id进行关联,
不使用这种形式
select s.id,s.name,t.name from student s,teacher t where s.tid=t.id
而使用嵌套查询
<!--
思路:
1. 查询所有的学生信息
2. 根据查询出来的学生的tid寻找特定的老师 (子查询)
由于学生信息里有老师这个对象,所以需要单独处理
-->
<select id="getStudent" resultmap="StudentTeacher">
select * from student
</select>
<resultmap id="StudentTeacher" type="student">
<result property="id" column="id">
<result property="name" column="name">
<!--复杂的属性,我们需要单独出来 对象:association 集合:collection
javaType:由于是一个对象,需要给对象设置类型
-->
<collection property="teacher" column="tid" javatype="teacher" select="getTeacher"> <!--这里的select就是嵌套查询-->
</collection></result></result></resultmap>
<select id="getTeacher" resulttype="teacher">
select * from teacher where id = #{id}
</select>
按照结果嵌套查询:
<!--按照结果进行查询-->
<select id="getStudent2" 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">
<result property="id" column="sid">
<result property="name" column="sname">
<association property="teacher" javatype="teacher">
<result property="name" column="tname"></result>
</association>
</result></result></resultmap>
13.一对多处理
学生实体类:
@Data
public class Student{
private int id;
private String name;
private int tid;
}
老师实体类:
@Data
public class Student{
private int id;
private String name;
//一个老师拥有多个学生
private List<student> students;
}
按照结果嵌套查询:
<!--按结果嵌套查询-->
<select id="getTeacher" resultmap="StudentTeacher">
SELECT s.id sid, s.name sname,t.name tname,t.id tid FROM student s, teacher t
WHERE s.tid = t.id AND tid = #{tid}
</select>
<resultmap id="StudentTeacher" type="Teacher">
<result property="id" column="tid">
<result property="name" column="tname">
<!--复杂的属性,我们需要单独处理 对象:association 集合:collection
javaType=""指定属性的类型!
集合中的泛型信息,我们使用ofType获取
-->
<collection property="students" oftype="Student">
<result property="id" column="sid">
<result property="name" column="sname">
<result property="tid" column="tid">
</result></result></result></collection>
</result></result></resultmap>
建议使用结果嵌套查询
小结:
javaTpye:用来指定实体类中属性的类型
ofType:用来指定映射到List或集合中的pojo类型,泛型中的类型
14.动态sql:
也就是sql语句,在sql语句中,执行一些逻辑代码,比如if,where,choose,when
主要用来解决根据不同条件拼接sql语句,比如拼接时不能忘记添加必要的空格,去掉列表最后一个列名的逗号
14.1 if语句:
固定格式
根据 username 和 sex 来查询数据。如果username为空,那么将只根据sex来查询;反之只根据username来查询
<select id="selectUserByUsernameAndSex" resulttype="user" parametertype="com.iweb.entity.User">
select * from user where
username=#{username}
and sex=#{sex}
</select>
14.2 if+where 语句:
这个“where”标签会知道如果它包含的标签中有返回值的话,它就插入一个‘where关键字。此外,如果标签返回的内容是以AND 或OR 开头的,则它会剔除掉。
比如说,username !=null不成立,执行的sql语句就会变成select * from user and sex=?
加上where标签后,会自动将and剔除
<select id="selectUserByUsernameAndSex" resulttype="user" parametertype="com.iweb.entity.User">
select * from user
username=#{username}
and sex=#{sex}
</select>
14.3 if+set 语句
set会动态前置SET关键字,同时也会删掉无关的逗号
<!-- 根据 id 更新 user 表的数据 -->
<update id="updateUserById" parametertype="com.iweb.entity.User">
update user u
<set>
<if test="username != null and username != ''">
u.username = #{username},<!--注意此处的,不要忽略了-->
</if>
<if test="sex != null and sex != ''">
u.sex = #{sex}
</if>
</set>
where id=#{id}
这样写,如果第一个条件 username 为空,那么 sql 语句为:update user u set u.sex=? where id=?
如果第一个条件不为空,那么 sql 语句为:update user u set u.username = ? ,u.sex = ? where id=?
14.4 choose(when,otherwise) 语句
有时候,我们不想用到所有的查询条件,只想选择其中的一个,查询条件有一个满足即可,使用 choose 标签可以解决此类问题,类似于 Java 的 switch 语句
<select id="selectUserByChoose" resulttype="com.iweb.entity.User" parametertype="com.iweb.entity.User">
select * from user
id=#{id}
and username=#{username}
and sex=#{sex}
</select>
也就是说,这里我们有三个条件,id,username,sex,只能选择一个作为查询条件
如果 id 不为空,那么查询语句为:select * from user where id=?
如果 id 为空,那么看username 是否为空,如果不为空,那么语句为 select * from user where username=?;
如果 username 为空,那么查询语句为 select * from user where sex=?
14.5 trim 语句
trim标记是一个格式化的标记,可以完成set或者是where标记的功能。
也就是满足条件就将对应的条件写在sql语句中
prefix前缀,prefixOverrides前缀覆盖。
suffix后缀,suffixOverrides后缀覆盖。
<select id="selectUserByUsernameAndSex" resulttype="user" parametertype="com.iweb.entity.User">
select * from user
<!-- <where>
<if test="username != null">
username=#{username}
</if>
<if test="username != null">
and sex=#{sex}
</if>
</where> -->
<!--上下两个所展现的sql语句都是同一个-->
and username=#{username}
and sex=#{sex}
</select>
14.6 SQL 片段
有时候可能某个 sql 语句我们用的特别多,为了增加代码的重用性,简化代码,我们需要将这些代码抽取出来,然后使用时直接调用。
1.使用sql标签抽取公共的部分
比如:假如我们需要经常根据用户名和性别来进行联合查询,那么我们就把这个代码抽取出来
<!-- 定义 sql 片段 -->
<sql id="selectUserByUserNameAndSexSQL">
<if test="username != null and username != ''">
AND username = #{username}
</if>
<if test="sex != null and sex != ''">
AND sex = #{sex}
</if>
</sql>
2.在需要使用的地方使用include标签引用即可
引用 sql 片段
<select id="selectUserByUsernameAndSex" resulttype="user" parametertype="com.iweb.entity.User">
select * from user
<!-- trim 标记是一个格式化的标记,可以完成set或者是where标记的功能
prefix:前缀
prefixoverride:去掉第一个and或者是or
-->
<!-- 引用 sql 片段,如果refid 指定的不在本文件中,那么需要在前面加上 namespace -->
<!-- 在这里还可以引用其他的 sql 片段 -->
</select>
注意事项:
1.最好基于单表来定义sql片段,因为多表联合查询sql复杂,再引用可能会导致前面的sql语句不匹配
2.不要存在where标签
14.7 foreach语句
需求:我们需要查询 user 表中 id 分别为1,2,3的用户
sql语句:select * from user where id=1 or id=2 or id=3
select * from user where id in (1,2,3)
<!-- 改写select * from user where id=1 or id=2 or id=3 -->
<select id="selectUserByListId" parametertype="list" resulttype="com.iweb.entity.User">
select * from user
<!--
collection:指定输入对象中的集合属性
item:每次遍历生成的对象
open:开始遍历时的拼接字符串
close:结束时拼接的字符串
separator:遍历对象之间需要拼接的字符串
select * from user where 1=1 and (id=1 or id=2 or id=3)
-->
id=#{id}
</select>
<!-- 改写select * from user where id in(1,2,3) -->
<select id="selectUserByListId" parametertype="list" resulttype="com.iweb.entity.User">
select * from user
<!--
collection:指定输入对象中的集合属性
item:每次遍历生成的对象
open:开始遍历时的拼接字符串
close:结束时拼接的字符串
separator:遍历对象之间需要拼接的字符串
select * from user where 1=1 and id in (1,2,3)
-->
#{id}
</select>
小结:
动态sql就是在拼接sql语句,我们需要保证sql的正确性,按照sql的格式,组合在一起就可以了
建议:先写出完整的sql,再对应去修改成动态sql实现通用
15.缓存
15.1简介
1.什么是缓存[Cache]?
存在内存中的临时数据
将用户经常查询的数据放在缓存(内存)中,用户去查询数据就不用从磁盘上(关系型数据库文件)查询,从缓存中查询,从而提高查询效率,解决了高并发系统的性能问题
2.为什么使用缓存?
减少和数据库的交互次数,减少系统开销,提高系统效率
3.什么样的数据可以使用缓存?
经常查询并且不经常改变的数据 【可以使用缓存】
15.2mybatis缓存
MyBatis系统中默认定义了两级缓存:一级缓存和二级缓存
-
默认情况下,只有一级缓存开启(SqlSession级别的缓存,也称为本地缓存)
-
二级缓存需要手动开启和配置,他是基于namespace级别的缓存。
-
为了提高可扩展性,MyBatis定义了缓存接口Cache。我们可以通过实现Cache接口来定义二级缓存。
15.3一级缓存
一级缓存也叫本地缓存:SqlSession
缓存只在一个事务中有效,即同一个事务中先后执行多次同一个查询,只在第一次真正查询数据库,并将结果缓存,之后的查询都直接 获取缓存中的数据,如果是不同的事务,则缓存是隔离的
mybatis一级缓存默认又称为session级缓存,默认就是开启状态,且无法手动关闭
注意,在同一个事务中,如果发生了的修改更新操作(也就是增删改),一级缓存会失效
缓存失效的情况:
- 查询不同的东西
- 增删改操作,可能会改变原来的数据,所以必定会刷新缓存
- 查询不同的Mapper.xml
- 手动清理缓存
SqlSession.clearCache();
15.4二级缓存
mybatis的二级缓存默认是关闭的,需手动开启,且默认情况下,如果事务没有提交,二级缓存是失效的。另外,想要被二级缓存缓存的对象必须实现序列化结果
- 基于namespace级别的缓存,一个名称空间,对应一个二级缓存
- 工作机制
- 一个会话查询一条数据,这个数据就会被放在当前会话的一级缓存中
- 如果会话关闭了,这个会员对应的一级缓存就没了;但是我们想要的是,会话关闭了,一级缓存中的数据被保存到二级缓存中
- 新的会话查询信息,就可以从二级缓存中获取内容
- 不同的mapper查询出的数据会放在自己对应的缓存(map)中
缓存全局有效,一个事务查询的一个sql得到结果,会被缓存起来,之后只要缓存未被清除,则其他事务如果查询同一个sql,得到的将会是之前缓存的结果,二级缓存作用范围大,作用时间长,可能造成的危害也更大,所以在开发中一般很少启用二级缓存
二级缓存又称为SqlSessionFactory级缓存,可以在不同的session间缓存
当启用了二级缓存时,mybatis的查询顺序是,二级缓存-->一级缓存-->数据库
手动开启二级缓存:
主配置文件
<!--开启二级缓存 总开关 -->
<settings>
<setting name="cacheEnabled" value="true">
</setting></settings>
mapper.xml
<cache>
举例:
注意:只有查询才有缓存,根据数据是否需要缓存(修改是否频繁选择是否开启)useCache=“true”
<select id="getUserById" resulttype="user" usecache="true">
select * from user where id = #{id}
</select>
小结:
1.只要开启了二级缓存,同一个Mapper下就有效
2.所有的数据都会先放在一级缓存中
3.只要当会话提交,或者关闭的时候,才会提交到二级缓存中
4.当启用了二级缓存时,mybatis的查询顺序是,二级缓存-->一级缓存-->数据库