Mybatis
官方参考文档:(https://mybatis.org/mybatis-3/index.html)
- mybatis是一款持久层框架
- 支持定制化SQL,存储过程以及高级映射
- mybatis避免了几乎所有的JDBC代码,手动设置参数记忆获取结果集
- 可以使用XML或注解配置和映射原生类型、接口和Java的pojo为数据库中的记录。
如何获得Mybatis?
-
从GitHub网站上获取:(https://github.com/mybatis/mybatis-3/releases)
-
maven仓库:
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.6</version>
</dependency>
数据持久化:将程序的数据在持久状态和瞬时状态转化的过程
内存:断带即失
持久层:
- Dao层
- Service层y
- Servlet层
为什么需要mybatis?
-
传统的JDBC代码太复杂,简化
-
简单,灵活
1、第一个mybatis程序
思路:搭建环境–导入mybatis–编写代码–测试!
1.1、搭建数据库
CREATE DATABASE `mybatis`;
USE `mybatis`;
CREATE TABLE `user`(
`id` INT(20) NOT NULL,
`name` VARCHAR(30) DEFAULT NULL,
`pwd` VARCHAR(30) DEFAULT NULL,
PRIMARY KEY(`id`)
)ENGINE INNODB DEFAULT CHARSET=utf8;
INSERT INTO `user`(`id`,`name`,`pwd`) VALUES
(1,'张三','123456'),
(2,'李四','123456'),
(3,'王五','123756')
1.2、创建项目,导入jar包
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.16</version>
</dependency>
<!--mybatis-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.6</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
</dependencies>
1.3、mybatis核心配置文件
写在Resources文件下
<?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>s
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=false&useUnicode=true&characterEncoding=UTF-8"/>
<property name="username" value="root"/>
<property name="password" value="123456789"/>
</dataSource>
</environment>
</environments>
</configuration>
1.从xml构建SqlSessionFactory
2.从 SqlSessionFactory 获取 SqlSession
获取 SqlSession 的实例。
SqlSession 绝对包含对数据库执行 SQL 命令所需的所有方法。可以直接针对 SqlSession 实例执行映射的 SQL 语句
//SqlSessionFactory -- SqlSession
//第一步配置三步代码
public class MybatisUtils {
public static SqlSessionFactory sqlSessionFactory ;
static {
try {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
} catch (IOException e) {
e.printStackTrace();
}
}
//有了 SqlSessionFactory,顾名思义,您可以获取 SqlSession 的实例,通过openSession()方法
// SqlSession 绝对包含对数据库执行 SQL 命令所需的所有方法
public static SqlSession getSqlSession(){
return sqlSessionFactory.openSession();
}
}
1.4 编写代码
- Dao接口
public interface UserDao {
List<User> getUserList();
}
- 接口实现类由原来的UserDaoImpl转换为一个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">
<!--命名空间 绑定一个对应的Dao/Mapper接口-->
<mapper namespace="com.liu.dao.UserDao">
<!--查询语句,id对于接口方法的名字,返回的结果写泛型里的东西,写路径-->
<select id="getUserList" resultType="com.liu.pojo.User">
select * from mybatis.user
</select>
</mapper>
1.5、测试
注意点:错误:类型接口dao在mapper注册中未知
org.apache.ibatis.binding.BindingException: Type interface com.liu.dao.UserDao is not known to the MapperRegistry.
资源过滤问题:在target目录下没有生成资源文件,由于约定大于配置
java.lang.ExceptionInInitializerError
解决在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>
出现一字节的utf-8序列的字节1无效,是因为web文件中写了中文注释:
解决:将所有xml文件中的utf-8改为utf8
在xml文件中出现sql dialect is not configured
解决:在浅黄色区域按alt+enter,将generic SQL改为mysql即可查询到表
核心配置文件的资源路径下必须用/
在控制台输出查询语句时不能正常显示:
解决:可能是实体类创建时候没有toString方法
2、CRUD
namespace
namespace中的包名要和Dao/mapper接口的包名保持一致
select
选择,查询语句:
- id:就是对应的namespace中的方法名
- resultType:sql语句执行的返回值
- parameterType:参数类型
流程:
第一步:在UserMapper接口里写抽象方法
public interface UserMapper {
//查询全部用户
List<User> getUserList();
//根据id查询用户
User getUserById(int id);
//insert一个用户
int addUser(User user);
//修改用户
int updateUser(User user);
//根据id delete一个用户
int deleteById(int id);
}
第二步,在UserMapper.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.liu.dao.UserMapper">
<select id="getUserList" resultType="com.liu.pojo.User">
select * from mybatis.user
</select>
<select id="getUserById" parameterType="int" resultType="com.liu.pojo.User">
select * from mybatis.user where id = #{id}
</select>
<insert id="addUser" parameterType="com.liu.pojo.User">
insert into mybatis.user(id, name, pwd) VALUES (#{id},#{name},#{pwd});
</insert>
<update id="updateUser" parameterType="com.liu.pojo.User">
update mybatis.user
set name=#{name},pwd=#{pwd}
where id = #{id};
</update>
<delete id="deleteById" parameterType="int">
delete from mybatis.user where id=#{id};
</delete>
</mapper>
第三步,测试,注意:增删改需要提交事务
//测试类写在test文件下,和上面文件一一对应
public class UserMapperTest {
@Test
public void test(){
//获得sqlSession对象,
SqlSession sqlSession = MybatisUtils.getSqlSession();
//执行sql,getMapper接口类型,返回接口类型就可以使用接口方法
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> userList = mapper.getUserList();
for (User user : userList) {
System.out.println(user);
}
//关闭sqlSession
sqlSession.close();
}
@Test
public void getUserById(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user = mapper.getUserById(1);
System.out.println(user);
sqlSession.close();
}
//增删改需要提交事务
@Test
public void addUser(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
int res = mapper.addUser(new User(4, "泽泽", "123678"));
if (res>0){
System.out.println("插入成功");
}
//提交事务
sqlSession.commit();
sqlSession.close();
}
//更新
@Test
public void updateUser(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
int req = mapper.updateUser(new User(4, "天天", "123123"));
if (req>0){
System.out.println("修改成功");
}
sqlSession.commit();
sqlSession.close();
}
//删除用户
@Test
public void deleteById(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
int i = mapper.deleteById(4);
if (i>0){
System.out.println("删除成功");
}
sqlSession.commit();
sqlSession.close();
}
}
2.1、万能的Map
假设实体类或者数据库中的表,字段或者参数过多,应当考虑map!
int addUser2(Map<String,Object> map);
<insert id="addUser2" parameterType="map">
insert into mybatis.user(id, name, pwd) VALUES (#{idUser},#{nameUser},#{pwdUser});
</insert>
public void addUser2(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
Map<String,Object> map = new HashMap<>();
map.put("idUser",4);
map.put("nameUser","kaka");
int res = mapper.addUser2(map);
if (res>0){
System.out.println("插入成功");
}
//提交事务
sqlSession.commit();
sqlSession.close();
}
map传递参数,直接在sql中取出key即可
对象传递参数,直接在sql中取对象的属性即可
只有一个基本类型的时候可以直接在sql中获取!
2.2 模糊查询
//模糊查询
List<User> getUserLike(String value);
<select id="getUserLike" resultType="com.liu.pojo.User">
select * from mybatis.user where name like #{vaule}
</select>
<!-- select * from mybatis.user where name like #"%"{vaule}"%" 会造成sql注入问题,用户输入1 or 1=1-->
//模糊查询字段后面加 %查的字%
@Test
public void getUserLike(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> userList = mapper.getUserLike("%李%");
for (User user : userList) {
System.out.println(user);
}
sqlSession.commit();
sqlSession.close();
}
3、配置解析
3.1 核心配置文件
- mybatis-config.xml
3.2 环境变量(environments)
虽然您可以配置多个环境,但每个 SqlSessionFactory 实例只能选择一个环境。
因此,如果要连接到两个数据库,则需要创建两个 SqlSessionFactory 实例,每个实例对应一个。对于三个数据库,您需要三个实例,依此类推。
有两种 TransactionManager事务管理器:
- JDBC – 此配置仅直接使用 JDBC 提交和回滚工具。它依赖于从数据源检索到的连接来管理事务的范围。
- Managed– 此配置几乎不执行任何操作。它从不提交或回滚连接。
数据源:连接数据库
-
有三种内置数据源类型(即 type=“[UNPOOLED|POOLED|JNDI]“):
-
UNPOOLED – 此数据源实现只是在每次请求连接时打开和关闭连接。
-
POOLED – 此数据源实现将 JDBC 连接对象池化,外部响应更快
-
JNDI – 此数据源的实现旨在与容器(如 EJB 或应用程序服务器)一起使用,这些容器可以集中或外部配置数据源,并在 JNDI 上下文中放置对它的引用
mysql默认的的事务管理器是JDBC,连接池:POOLED
3.3 属性(properties)
属性外部设置可动态替换,可以在典型的 Java 属性文件(db.properties)实例中进行配置,也可以通过属性元素的子元素传入。
编写配置文件:
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis?useSSL=false&useUnicode=true&characterEncoding=utf-8
username=root
password=123456789
在核心配置文件中引入外部配置文件,如果两个外部文件中有同一个字段,优先使用外部配置文件
<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>
3.4 类型别名(typeAliases)
类型别名只是 Java 类型的较短名称。它仅与 XML 配置相关,并且只是为了减少完全限定类名的冗余类型而存在。
第一种方式:
<typeAliases>
<typeAlias type="com.liu.pojo.User" alias="User"/>
</typeAliases>
第二种方式:指定一个包名,mybatis会在包名下搜索JavaBean,用实体类的首字母小写代替的类名代替别名
<typeAliases>
<typeAlias type="com.liu.pojo"/>
</typeAliases>
扫描包的情况下,或者通过实体类上增加注解名来代替别名
@Alias("author")
常用内置别名:
_byte | 字节 |
---|---|
_long | 长 |
_short | 短 |
_int | int |
_integer | int |
_double | 双 |
_float | 浮 |
_boolean | 布尔 |
字符串 | 字符串 |
字节 | 字节 |
长 | 长 |
短 | 短 |
int | 整数 |
整数 | 整数 |
双 | 双 |
浮 | 浮 |
布尔 | 布尔 |
日期 | 日期 |
十进制 | 大十进制 |
大十进制 | 大十进制 |
对象 | 对象 |
地图 | 地图 |
哈希映射 | 哈希映射 |
列表 | 列表 |
数组列表 | 阵列列表 |
收集 | 收集 |
迭 代 | 迭 代 |
3.5 设置(settings)
比如数据库中的字段last_name对应实体类中的lastName,这个会自动帮助你生成!
日志实现:
开启缓存和懒加载:
其他配置:
plugins:
- mybatis-generator-core
- mybatis-plus
- 通用mapper
3.6 映射器(mappers)
未注册会报错!!
MapperRegistry:注册绑定Mapper文件,每写一个dao实现类就需要绑定
方式一(推荐):使用相对于类路径的资源引用,无论文件在哪都可以执行
<mappers>
<mapper resource="com/liu/dao/UserMapper.xml"/>
</mappers>
方式二:使用映射器接口实现类的完全限定类名,通过class
- 接口和Mapper配置文件必须在同一个包下且必须同名
<mappers>
<mapper class="com.liu.dao.UserMapper"/>
</mappers>
方式三:通过包名实现
- 接口和Mapper配置文件必须在同一个包下且必须同名
<mappers>
<package name="com.liu.dao.UserMapper"/>
</mappers>
3.7 生命周期和作用域
SqlSessionFactoryBuilder:
- 一旦创建就不需要它了
- 局部变量
SqlSessionFactory:
- 数据库连接池
- 一旦创建就一直存在,不能多次重建它
- 全局作用域
SqlSession:
- 连接到连接池的一个请求
- 关闭请求,会回收供下一个使用
- 方法或者请求作用域
3.8 解决数据库与字段不一致的问题
当数据库的字段与实体类的字段不一致的时候,会查出null值
解决方法:
- 别名:将sql查询的字段取别名
- resultMap结果映射集
3.resultMap标签
<resultMap id="UserMap" type="User">
<!--column数据库中的字段,property实体类中的属性-->
<result column="id" property="id"/>
<result column="name" property="name"/>
<result column="pwd" property="password"/>
</resultMap>
<select id="getUserById" resultMap="UserMap">
select * from mybatis.user where id = #{id}
</select>
4、 日志
4.1 日志工厂
如果一个数据库操作出现了异常,需要排错,日志就是最好的助手
曾经:debug,sout
现在:日志工厂
-
SLF4J
-
LOG4J(掌握)
-
LOG4J2
-
JDK_LOGGING
-
COMMONS_LOGGING
-
STDOUT_LOGGING(掌握)
-
NO_LOGGING
在Mybatis中具体使用哪个在核心配置文件中设置中设定!
<settings>
<!--标准的日志-->
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
打印日志:
4.2 LOG4J
控制日志输送的目的地是控制台,文件,GUI组件
控制每一天日志的输出格式
通过一个配置文件来灵活的进行配置,不需要修改应用的代码
1.导入log4j的包
<!-- https://mvnrepository.com/artifact/log4j/log4j -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
2.在resources文件下建立log4j.properties文件
#输出日志文件到console和file目的地
log4j.rootLogger=DEBUG,console,file
# 控制台(console)
log4j.appender.console=org.apache.log4j.ConsoleAppender
log4j.appender.console.Threshold=DEBUG
log4j.appender.console.Target=System.out
log4j.appender.console.layout=org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern=[%c]-%m%n
# 文件输出的相关设置(file)
log4j.appender.file=org.apache.log4j.RollingFileAppender
log4j.appender.file.Threshold=DEBUG
log4j.appender.file.File=D:/logs/log.log4j
log4j.appender.file.MaxFileSize=10mb
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
3.配置log4j为日志的实现
<settings>
<setting name="logImpl" value="LOG4J"/>
</settings>
4.log4j的使用
别导错包:import org.apache.log4j.Logger;
//测试类写在test文件下,和上面文件一一对应
public class UserMapperTest {
static Logger Logger = org.apache.log4j.Logger.getLogger(UserMapperTest.class);
@Test
public void getUserById(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
Logger.info("测试,进入getUserById方法");//提示信息
Logger.debug("debug:进入了特色testLog4j");//bug信息
Logger.error("error:进入了特色testLog4j");//错误信息
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user = mapper.getUserById(1);
System.out.println(user);
sqlSession.close();
}
}
5、分页
语法:SELECT * FROM user limit 开始查询到的地方,每一页显示的量;
只有一个参数,代表从第一个查到这个参数为止!
bug:每一页的显示量是-1,可以查到最后一个数据
//分页
List<User> getUserLimit(Map<String,Integer> map);
<select id="getUserLimit" parameterType="map" resultType="User">
select * from mybatis.user limit #{startIndex},#{PageSize}
</select>
@Test
public void getUserLimit(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);//从接口获取
HashMap<String, Integer> map = new HashMap<String,Integer>();
map.put("startIndex",0);
map.put("PageSize",2);
List<User> userLimit = mapper.getUserLimit(map);
for (User user : userLimit) {
System.out.println(user);
}
sqlSession.close();
}
5.1、RowBounds类
1.接口
//RowBounds
List<User> getUserRowBounds();
2.mapper.xml
<select id="getUserRowBounds" resultType="User">
select * from mybatis.user
</select>
3.测试
@Test
public void getUserRowBounds(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
//RowBounds实现
RowBounds rowBounds = new RowBounds(1,2);
//通过java代码层面实现分类
List<User> userList = sqlSession.selectList("com.liu.dao.UserMapper.getUserRowBounds",null,rowBounds);
for (User user : userList) {
System.out.println(user);
}
sqlSession.close();
}
6、使用注解开发
6.1、底层与核心本质
核心本质:通过反射来实现的,反射可以获取到所有的参数及返回类型
底层:动态代理模式:
- 静态代理实现较简单,代理类在编译期生成,效率高。缺点是会生成大量的代理类。需要代理类和真实对象实现同一个接口
- JDK动态代理不要求代理类和委托类实现同一个接口,但是委托类需要实现接口,代理类需要实现InvocationHandler接口。
- 动态代理要求代理类InvocationHandler接口,通过反射代理方法,比较消耗系统性能,但可以减少代理类的数量,使用更灵活。
动态代理模式:
class HelloServiceDynamicProxy {
private HelloService helloService;
public HelloServiceDynamicProxy(HelloService helloService) {
this.helloService = helloService;
}
public Object getProxyInstance() {
return Proxy.newProxyInstance(helloService.getClass().getClassLoader(), helloService.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("Before say hello...");
Object ret = method.invoke(helloService, args);
System.out.println("After say hello...");
return ret;
}
});
}
}
6.2、面向接口编程
真正开发中使用面向接口编程,根本原因:解耦,定义(规范,约束)与实现(名实分离的原则)的分离!
简单的select查询可以用注解,复杂的还是要用xml来配置,比如数据库的字段与实例类的字段不同,需要用到resultMap结果集映射就必须得用xml实现
1.接口加注解
@Select("select * from mybatis.user")
List<User> getUser();
2.测试
public void test(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> user = mapper.getUser();
for (User user1 : user) {
System.out.println(user1);
}
sqlSession.close();
}
7、Mybatis的执行流程
public static SqlSession getSqlSession(){
return sqlSessionFactory.openSession(true);
}
通过在openSession里设置为true,就不需要手动提交事务了
7.1、增删改
方法存在多个参数,所有基本类型参数前面必须加上Param注解,Param里的参数就是#{}取的值,引用类型(比如User类)不需要加Param
面试题:#{}与${}区别:
#{}可以防止sql注入的问题,${}不行,两者就像preStatemt和Statement的区别
1.接口
@Select("select * from mybatis.user")
List<User> getUser();
//方法存在多个参数,所有基本类型参数前面必须加上Param注解,Param里的参数就是#{}取的值
//引用类型(比如User类)不需要加Param
@Select("select * from mybatis.user where id = #{id}")
User getUserById(@Param("id") 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 deleteUserById(@Param("id") int id);
(必须在核心配置文件中配置映射文件)
<mappers>
<mapper resource="com/liu/dao/UserMapper.xml"/>
</mappers>
2.测试
@Test
public void test(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
// List<User> user = mapper.getUser();
// for (User user1 : user) {
// System.out.println(user1);
// }
User userById = mapper.getUserById(1);
System.out.println(userById);
sqlSession.close();
}
@Test
public void test2(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
// int i = mapper.addUser(new User(5, "周周", "12323431"));
// int i = mapper.updateUser(new User(5, "依依", "123231"));
int i = mapper.deleteUserById(5);
if (i>0){
System.out.println("删除成功!");
}
sqlSession.close();
}
8、Lombok
再也不用写get/set方法!在实体类上加注解
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private int id;
private String name;
private String pwd;
}
1.下载lombok
在setting的plugins下载!
2.导入jar包
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.20</version>
<scope>provided</scope>
</dependency>
@Data://无参构造,get/set,tostring,hashcode,equals
@AllArgsConstructor //有参构造
@NoArgsConstructor //无参构造
9、多对一的处理
多张表关联一个表!
association
:一个复杂类型的关联,许多结果将包装成这种类型
- 嵌套结果映射-关联本身可以是一个resultMap元素,或者从别处引用一个
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');
环境搭建:
1.导入lombok
2.新建实体类Teacher,Student
3.建立Mapper接口
4.建立Mapper.xml文件,保证在同一个包名下面
5.配置核心配置文件mappers注册绑定Mapper接口
6.测试
按照查询嵌套处理
直接各自查询出两张表,再通过子查询的方式
注意点:
1.resultType里面对应的是实体类的名字也就是pojo
2.查询的id就是接口方法名字
3.老师表是一个对象需要用到结果集映射的association
4.javaType是指定的属性名
思路:
1.查询所有学生信息
2.根据查询出来的学生的tid,寻找对应的老师
接口方法:
//查询所有的学生信息以及对应的老师的信息
public List<Student> getStudent();
xml配置:
property是实体类中的名字,column是数据库中的名字
<mapper namespace="com.liu.dao.StudentMapper">
<select id="getStudent" resultMap="StudentTeacher">
select * from mybatis.student;
</select>
<resultMap id="StudentTeacher" type="Student">
<result property="id" column="id"/>
<result property="name" column="name"/>
<!--复杂的属性 对象用association 老师是对象-->
<association property="teacher" column="tid" javaType="Teacher" select="getTeacher"/>
</resultMap>
<select id="getTeacher" resultType="Teacher">
select * from mybatis.teacher where id = #{id};
</select>
</mapper>
测试:
@Test
public void testStudent(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
List<Student> studentList = mapper.getStudent();
for (Student student : studentList) {
System.out.println(student);
}
sqlSession.close();
}
按照结果嵌套处理
联表查询
<select id="getStudent2" resultMap="StudentTeacher2">
select s.id sid,s.name sname,t.name tname
from mybatis.student s,mybatis.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"/>
</association>
</resultMap>
10、一对多的处理
比如:一个老师对应多个学生!
list集合要用ofType类型
实体类:
@Data
public class Teacher {
private int id;
private String name;
//一个老师拥有多个学生
private List<Student> students;
}
@Data
public class Student {
private int id;
private String name;
private int tid;
}
查询出来的结果:
Teacher(id=1, name=秦老师, students=[Student(id=1, name=小明, tid=1), Student(id=2, name=小红, tid=1), Student(id=3, name=小张, tid=1), Student(id=4, name=小李, tid=1), Student(id=5, name=小王, tid=1)])
按结果查询
接口配置:
//获取指定老师下的所有学生及老师的信息
Teacher getTeacher(@Param("tid") int id);
xml配置:
<select id="getTeacher" resultMap="TeacherStudent">
select s.id sid,s.name sname,t.name tname,t.id tid
from mybatis.student s,mybatis.teacher t
where s.tid = t.id and t.id = #{tid}
</select>
<resultMap id="TeacherStudent" type="Teacher">
<result property="id" column="tid"/>
<result property="name" column="tname"/>
<collection property="students" ofType="Student">
<result property="id" column="sid"/>
<result property="name" column="sname"/>
<result property="tid" column="tid"/>
</collection>
</resultMap>
测试:
public static void main(String[] args) {
SqlSession sqlSession = MybatisUtils.getSqlSession();
TeacherMapper mapper = sqlSession.getMapper(TeacherMapper.class);
Teacher teacher = mapper.getTeacher(1);
System.out.println(teacher);
sqlSession.close();
}
按子查询方式
@Data
public class Teacher {
private int id;
private String name;
//一个老师拥有多个学生
private List<Student> students;
}
javaType是用来指定实体类中属性的类型
offType用来指定映射到List集合中的pojo类型,泛型中的约束类型!
接口:
Teacher getTeacher2(@Param("tid") int id);
xml配置:
<select id="getTeacher2" resultMap="TeacherStudent2">
select * from mybatis.teacher where id = #{tid};
</select>
<resultMap id="TeacherStudent2" type="Teacher">
<result property="id" column="id"/>
<result property="name" column="name"/>
<collection property="students" column="id" javaType="ArrayList" ofType="Student" select="getStudentByTeacherId"/>
</resultMap>
<select id="getStudentByTeacherId" resultType="Student">
select * from mybatis.student where tid = #{tid};
</select>
测试:
public void test(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
TeacherMapper mapper = sqlSession.getMapper(TeacherMapper.class);
Teacher teacher = mapper.getTeacher2(1);
System.out.println(teacher);
sqlSession.close();
}
11、动态sql
动态sql就是根据不同的条件生成不同的sql语句,也就是拼接sql语句,保证sql的正确性即可
字段不一致,解决:在核心配置文件的setting下的mapUnderscoreToCamelCase驼峰命名
动态sql和JSTL类似于XML的文本处理器
CREATE TABLE `blog`(
`id` VARCHAR(50) NOT NULL COMMENT '博客id',
`title` VARCHAR(100) NOT NULL,
`author` VARCHAR(30) NOT NULL,
`create_time` DATETIME NOT NULL,
`views`INT(30) NOT NULL COMMENT '浏览量'
)ENGINE=INNODB DEFAULT CHARSET=utf8
1.导包
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.20</version>
<scope>provided</scope>
</dependency>
</dependencies>
2.写工具类和pojo实体类
@Data
public class Blog {
private int id;
private String title;
private String author;
private Date createTime;
private int views;
}
3.写接口和对应的Mapper
<mapper namespace="com.liu.dao.BlogMapper">
<insert id="addBolg" parameterType="blog">
insert into mybatis.blog(id, title, author, create_time, views)
values (#{id},#{title},#{author},#{createTime},#{views});
</insert>
</mapper>
4.在核心配置文件中进行映射配置
如果运行测试时不成功,在maven里面clean一下!
<mappers>
<mapper class="com.liu.dao.BlogMapper"/>
</mappers>
if
正常的sql语句:
<select id="queryBlogIF" parameterType="map">
select * from mybatis.blog where title = #{title} and author = #{author};
</select>
用if:
<select id="queryBlogIF" parameterType="map" resultType="Blog">
select * from mybatis.blog where 1=1
<if test="title != null">
and title = #{title}
</if>
<if test="author != null">
and author = #{author}
</if>
</select>
@Test
public void test(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
HashMap hashMap = new HashMap();
hashMap.put("title","sql学习");
hashMap.put("author","liuxiang");
List<Blog> blogs = mapper.queryBlogIF(hashMap);
for (Blog blog : blogs) {
System.out.println(blog);
}
sqlSession.close();
}
choose(when,ohterwise)
类似于switch语句!
<select id="queryBlogChoose" parameterType="map" resultType="Blog">
select * from mybatis.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>
trim(where,set)
where标签会自动判断是不是第一个and,是第一个就自动去除,不是第一个就不取除!什么都不传就自动去除where标签!
set会自动去除逗号!
<update id="updateBlog" parameterType="map">
update mybatis.blog
<set>
<if test="title != null">
title = #{title},
</if>
<if test="author != null">
author = #{author}
</if>
where id = #{id}
</set>
</update>
测试:
@Test
public void updateBlog(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
HashMap hashMap = new HashMap();
hashMap.put("title","sql学习22");
hashMap.put("author","liuxiang");
hashMap.put("id","9faf250d90df4c258dbdfbf59a210d94");
// hashMap.put("views",9999);
mapper.updateBlog(hashMap);
sqlSession.close();
}
所谓的动态sql本质还是sql语句,只是在sql层面执行一个逻辑代码!
foreach
SQL片段:实现代码复用
- 最好基于单表来定义sql片段
- 不要存在where标签
<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 mybatis.blog
<where>
<include refid="if-title-author"></include>
</where>
</select>
foreach:
接口:
//查询1-3号的博客
List<Blog> queryBlogForeach(Map map);
xml配置:
<!--
原来sql:
select * from mybatis.blog where 1=1 and (id=1 or id=2 or id=3)
-->
<select id="queryBlogForeach" parameterType="map" resultType="blog">
select * from mybatis.blog
<where>
<foreach collection="ids" item="id" open="and (" close=")" separator="or">
id = #{id}
</foreach>
</where>
</select>
测试:
@Test
public void queryBlogForeach(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
HashMap map = new HashMap();
ArrayList<Integer> ids = new ArrayList<>();
ids.add(1);
ids.add(2);
map.put("ids",ids);
List<Blog> blogs = mapper.queryBlogForeach(map);
for (Blog blog : blogs) {
System.out.println(blog);
}
sqlSession.close();
}
结果:
12、缓存
12.1、简介
查询:连接数据库,耗资源!
一次查询的结果,暂存在可以取到的地方—缓存,再次查询数据的时候直接走缓存,不用访问数据库!
读写分离,主从复制!!
有专门的数据库供读,专门的数据库供写!
1.什么是缓存?
- 存在内存中的临时数据
- 将用户经常查询的数据放在缓存中,用户去查询数据就不用从磁盘上(关系型数据库)查询,从缓存中查询。从而提高了查询效率,解决高并发的性能问题!
2.为什么使用缓存?
- 减少和数据库的交互次数,减少系统开销,提高系统效率
3.什么样的数据能够使用缓存?
- 经常查询并且不经常改变的数据。
12.2、Mybatis缓存
mybatis系统默认定义两个缓存:一级缓存和二级缓存
- 默认情况下,只有一级缓存开启(sqlSession级别的缓存,也称为本地缓存)
- 二级缓存需要手动开启和配置,是基于namespace级别的缓存
- 为了提高扩展性,mybatis定义了缓存接口Cache,可以通过Cache接口来定义二级缓存
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;
}
}
12.3、一级缓存
- 一级缓存也叫本地缓存:
- 与数据库同一次会话期间查询到的数据会放到本地缓存中
- 以后需要获取相同的数据,直接从缓存中拿
1.开启日志
2.测试在一个Session中查询两次相同的记录
3.查看日志输出
缓存失效的情况
1.查询不同的东西
2.增删改操作,可能会改变原来的数据,所以必定会刷新缓存
走了两次查询!
3.查询不同的Mapper.xml
4.手动清理缓存
@Test
public void queryUserById(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user = mapper.queryUserById(1);
System.out.println(user);
// mapper.updateUser(new User(2,"kaka","bbb"));
sqlSession.clearCache();//手动清理缓存
System.out.println("===============");
User user2 = mapper.queryUserById(1);
System.out.println(user2);
System.out.println(user==user2);
sqlSession.close();
}
12.4、二级缓存
开启全局缓存:在核心配置文件中的setting下配置:
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
<!--开启二级缓存-->
<setting name="cacheEnabled" value="true"/>
</settings>
要启用二级缓存,只需要在要使用的Mapper映射文件中添加一行:
<cache
eviction="FIFO"
flushInterval="60000"
size="512"
readOnly="true"/>
<!--输入输出流 60s刷新一次 最大缓存数512 -->
- 二级缓存也叫全局缓存,一级缓存作用域太低了
- 基于namespace级别的缓存,一个名称空间对应一个二级缓存
- 工作机制:
- 一个会话查询一条数据,这个数据就会放在当前会话的一级缓存中
- 如果当前会话关闭了这个会话对应的一级缓存就没了,需要的是一级缓存关闭了,数据被保存在二级缓存中
- 新的会话查询信息就可以从二级缓存中获取内容
- 不同的mapper查出的数据会放在字节对应的缓存(map)中
<cache
eviction="FIFO"
flushInterval="60000"
size="512"
readOnly="true"/>
<select id="queryUserById" resultType="User" useCache="true">
select * from mybatis.user where id = #{id}
</select>
测试:
@Test
public void queryUserById(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
SqlSession sqlSession2 = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user = mapper.queryUserById(1);
System.out.println(user);
sqlSession.close();//第一个session关闭了,将数据存在二级缓存中,只用查一次数据库
UserMapper mapper2 = sqlSession2.getMapper(UserMapper.class);
User user2 = mapper2.queryUserById(1);
System.out.println(user2);
System.out.println(user==user2);
sqlSession2.close();
}
结果:
第一个session关闭了,将数据存在二级缓存中,只用查一次数据库
问题:无策略的时候,需要将实体类序列化,不然会报错!
public class User implements Serializable {
private int id;
private String name;
private String pwd;
}
12.5、缓存原理
缓存顺序:
1.先看二级缓存
2.再看一级缓存
3.查询数据库
12.6、自定义缓存-ehcache
1.导包
<!-- https://mvnrepository.com/artifact/org.mybatis.caches/mybatis-ehcache -->
<dependency>
<groupId>org.mybatis.caches</groupId>
<artifactId>mybatis-ehcache</artifactId>
<version>1.2.1</version>
</dependency>