MyBatis框架自学笔记
MyBatis官网:https://mybatis.org
视频:BV1NE411Q7Nx
文章目录
1、简介
1.1 什么是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。
获取MyBatis
-
Github上下载源码
-
mavenrepository获取依赖
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.9</version> </dependency>
1.2 持久化
目的:数据持久化,长期保存
- 持久化就是将程序的数据在持久状态和瞬时状态转化的过程
- 内存:断电即失
- 数据库、io文件可以实现持久化
1.3 持久层
定义:完成持久化工作的代码块
1.4 为什么需要MyBatis
- 使用方便
- 传统的JDBC代码比较复杂,MyBatis提供了代码简化的框架。
- 优点
- 简单易学
- 灵活
- sql和代码分离,提高了可维护性
- 提供映射标签,支持对象与数据库的orm字段关系映射
- 提供对象关系映射标签,支持对象关系组件维护
- 提供xml标签,支持编写动态sql
2、第一个MyBatis程序
搭环境–>导入相关依赖–>coding–>test
2.1 搭环境
搭数据库
CREATE DATABASE mybatis;
USE mybatis;
CREATE TABLE user(
id INT(20) NOT NULL PRIMARY KEY,
name VARCHAR(30) DEFAULT NULL,
pwd VARCHAR(30) DEFAULT NULL
)ENGINE=INNODB DEFAULT CHARSET=utf8;
INSERT INTO user(id,name,pwd) VALUES
(1,'张三','123456'),
(2,'李四','123456'),
(3,'王五','123456');
新建maven项目
-
建立一个普通的maven项目
-
删除src文件(该项目作为父工程使用)
-
导包
<!-- 导入依赖--> <dependencies> <!-- mysql驱动--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.47</version> </dependency> <!-- mybatis--> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.9</version> </dependency> <!-- junit--> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> </dependency> </dependencies>
2.2 创建一个模块
-
编写mybatis的核心配置文件
src.main.resources.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> <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=true&useUnicode=true&characterEncoding=UTF-8"/> <property name="username" value="root"/> <property name="password" value="a13587491757A"/> </dataSource> </environment> </environments> </configuration>
-
编写mybatis工具类
package com.infinite.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; //sqlSessionFactory-->sqlSession public class MyBatisUtils { private static SqlSessionFactory sqlSessionFactory; static{ try{ //①:使用MyBatis获取sqlSessionFactory对象 String resource="mybatis-config.xml"; InputStream inputStream= Resources.getResourceAsStream(resource); sqlSessionFactory=new SqlSessionFactoryBuilder().build(inputStream); } catch (IOException e){ e.printStackTrace(); } } //有了SqlSessionFactory,我们就可以通过它获取SqlSession实例 //SqlSession完全包含了面向数据库执行SQL命令所需的所有方法 public static SqlSession getSqlSession(){ return sqlSessionFactory.openSession(); } }
2.3 写代码
-
实体类
package com.infinite.pojo; public class User { private int id; private String name; private String pwd; //constructor //getter and setter //toString }
-
Dao接口
com.infinite.dao.UserDao
package com.infinite.dao; import com.infinite.pojo.User; import java.util.List; public interface UserDao { public List<User> getUserList(){} }
-
Dao接口实现类
com.infinite.dao.UserMapper.xml --> 配置文件
取代了之前javaweb时所写的 UserDaoImp1.java
<?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"> <!--namespqce绑定一个对应的Dao/Mapper接口--> <mapper namespace="com.infinite.dao.UserDao"> <!-- select查询语句--> <select id="getUserList" resultType="com.infinite.pojo.User"> select * from mybatis.user </select> </mapper>
2.4 测试
利用junit测试。
为了明确测试目的,可以额外写一个测试目录,目录结构与源代码结构相同,如图:
test.java.com.infinite.dao.UserDaoTest.java
public class UserDaoTest {
@Test
public void test(){
//①: 获得SqlSession对象
SqlSession sqlSession = MyBatisUtils.getSqlSession();
//方法一: getMapper
UserDao mapper = sqlSession.getMapper(UserDao.class);
List<User> userList = mapper.getUserList();
//方法二: 不常用
//List<Object>objects=sqlSession.selectList("com.infinite.UserDao.getUserList");
for (User user : userList) {
System.out.println(user.toString());
}
//关闭SqlSession
sqlSession.close();
}
}
测试效果
可能遇到的问题
org.apache.ibatis.binding.BindingException: Type interface com.infinite.dao.UserDao is not known to the MapperRegistry
要在核心配置文件中注册mappers
<!-- 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>
<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=true&useUnicode=true&characterEncoding=UTF-8"/>
<property name="username" value="用户"/>
<property name="password" value="密码"/>
</dataSource>
</environment>
</environments>
<!-- 每一个Mapper.xml都需要在MyBatis核心文件中注册-->
<mappers>
<mapper resource="com/infinite/dao/UserMapper.xml"/>
</mappers>
</configuration>
其他可能的问题:
- 配置文件没有注册
- 绑定接口错误
- 方法名不对
- 返回类型不对
- Maven导出资源问题(要在pom.xml中设置build标签)
3、增删改查(CRUD)
namespace中的包名要和Dao/Mapper接口的包名一致
标签内常见的变量:
- id:对应的namespace中国的方法名
- resultType:Sql语句执行的返回值
- parameterType:传入的参数类型
3.1 编写思路
-
编写接口
com.infinite.dao.UserDao
public interface UserDao { //查询全部用户 public List<User> getUserList(); //根据id查询用户 User getUserById(int id); //插入一个用户记录 int addUser(User user); //删除一个用户 int deleteUser(int id); }
-
编写对应的mapper中的sql语句
com.infinite.dao.UserMapper.xml
<!-- select查询语句--> <select id="getUserList" resultType="com.infinite.pojo.User"> select * from mybatis.user; </select> <select id="getUserById" parameterType="int" resultType="com.infinite.pojo.User"> select * from mybatis.user where id=#{id}; </select> <!-- 对象中的属性,可以直接取出--> <insert id="addUser" parameterType="com.infinite.pojo.User"> insert into mybatis.user(id,name,pwd) values(#{id},#{name},#{pwd}); </insert> <!-- 修改用户--> <update id="updateUser" parameterType="com.infinite.pojo.User"> update mybatis.user set name=#{name},pwd=#{pwd} where id=#{id}; </update> <!-- 删除用户--> <delete id="deleteUser" parameterType="int"> delete from mybatis.user where id=#{id}; </delete>
-
测试
test.java.com.infinite.dao.UserDaoTest
public class UserDaoTest { @Test public void test(){ //①: 获得SqlSession对象 SqlSession sqlSession = MyBatisUtils.getSqlSession(); //方法一: getMapper UserDao mapper = sqlSession.getMapper(UserDao.class); List<User> userList = mapper.getUserList(); for (User user : userList) { System.out.println(user.toString()); } //关闭SqlSession sqlSession.close(); } @Test public void getUserById(){ //获取SqlSession对象 SqlSession sqlSession=MyBatisUtils.getSqlSession(); UserDao mapper = sqlSession.getMapper(UserDao.class); User user=mapper.getUserById(2); System.out.println(user.toString()); sqlSession.close(); } @Test public void addUser(){ SqlSession sqlSession=MyBatisUtils.getSqlSession(); UserDao userDao=sqlSession.getMapper(UserDao.class); userDao.addUser(new User(4,"赵六","963852")); //提交更改 sqlSession.commit(); sqlSession.close(); } @Test public void updateUser(){ SqlSession sqlSession=MyBatisUtils.getSqlSession(); UserDao userDao=sqlSession.getMapper(UserDao.class); userDao.updateUser(new User(4,"赵六","000000")); //提交更改 sqlSession.commit(); sqlSession.close(); } @Test public void deleteUser(){ SqlSession sqlSession=MyBatisUtils.getSqlSession(); UserDao userDao=sqlSession.getMapper(UserDao.class); userDao.deleteUser(4); //提交更改 sqlSession.commit(); sqlSession.close(); } }
3.2 使用Map简化参数
假如实体类或者数据库中的表,字段或参数过多,可以考虑使用Map简化传入参数的写法。
接口
int addUser2(Map<String,Object> map);
配置文件
<insert id="addUser2" parameterType="map">
insert into mybatis.user(id,pwd) values (#(userid),#(password));
</insert>
测试
@Test
public void addUser2(){
SqlSession sqlSession=MybatisUtils.getSqlSession();
UserDao mapper=sqlSession.getMapper(UserDao.class);
Map<String,Object>map=new HashMap<String,Object>();
map.put("userid",5);
map.put("password","123456789");
mapper.addUser2(map);
sqlSession.close();
}
Map传递参数,直接在sql中取出key即可 parameterType="map"
对象传递参数,直接在sql中取出对象的属性名即可 parameterType="(Object)"
只有一个基本类型参数的情况下,可以直接在sql中取到
传入参数多的情况下可以使用map,或特殊注解。
3.3 模糊查询
接口
List<User> getUserLike(String s);
配置文件
<select id="getUserLike" resultType="com.infinite.poji.User">
select * from mybatis.user where name like #{value};
</select>
其实也可以写成 %#{value}%
的形式,但是这样可能导致SQL注入(第三方给sql语句恶意添加查询条件,从而查询到非公开数据)。
test
@Test
public void getUserLike(){
SqlSession sqlSession=MybatisUtils.getSqlSession();
UserDao mapper=sqlSession.getMapper(UserDao.class);
List<User> userList=mapper.getUserLike("%张%");
for(User user:userList){
System.out.println(user);
}
sqlSession.close();
}
常见错误
-
标签匹配出错。确定好当前namespace下,什么方法名对应什么sql操作。比如,
deleteUser(int id)
会作为delete标签的id,而不是其他sql操作 -
resource
绑定mapper,要用’/‘路径。namespace
则是用’.’<mapper namespace="com.infinite.dao.UserDao"> <mappers> <mapper resource="com/infinite/dao/UserMapper.xml"/> </mappers>
-
程序配置文件要符合规范
-
NullPointerException,可能是因为没有注册到资源
-
输出的xml文件中有可能存在中文乱码问题。
-
maven资源导出问题(target生成目录中没有xml文件)。在pom.xml中添加build标签,完善其中的内容。
4、配置解析
4.1 核心配置文件
-
mybatis-config.xml (官方推荐命名
-
MyBatis的配置文件包含了会深刻影响MyBatis行为的设置和属性信息
configuration(配置) properties(属性) <--重要 settings(设置) <--重要 typeAliases(类型别名) <--重要 typeHandlers(类型处理器) 不用了解 objectFactory(对象工厂) 不用了解 plugins(插件) 不用了解 environments(环境配置) environment(环境变量) transactionManager(事务管理器) dataSource(数据源) databaseIdProvider(数据库厂商标识) mappers(映射器)
4.2 environments(环境配置)
MyBatis 可以配置成适应多种环境
不过要记住:尽管可以配置多个环境,但每个 SqlSessionFactory 实例只能选择一种环境。
要学会使用配置多套运行环境。具体步骤可以直接看官方文档(XML配置–>环境配置)
MyBatis默认的事务管理器时JDBC,连接池:POOLED
<environments default="development"> <!--默认选择的环境id-->
<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?useUnicode=true&characterEncoding=UTF-8"/>
<property name="username" value="用户"/>
<property name="password" value="密码"/>
</dataSource>
</environment>
<environment id="test">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=UTF-8"/>
<property name="username" value="用户"/>
<property name="password" value="密码"/>
</dataSource>
</environment>
</environments>
4.3 properties(属性)
我们可以通过peoperties属性来实现引用配置文件
这些属性都是可外部配置且可动态替换的,既可以在典型的java属性文件中配置,也可以通过peoperties元素的子元素来传递。如之前写JDBC时创建的db.properties
文件
resources/db.properties
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=UTF-8
username=root
password=a13587491757A
在核心配置文件中引入(注意标签顺序)
<configuration>
<!-- 引入外部配置文件-->
<properties resource="db.properties">
<!-- 也可以将需要的属性写在这里,优先级更低-->
<!-- <property name="" value=""/>-->
</properties>
</configuration>
- 可以直接引入外部文件
- 可以在其中增加一些属性配置
- 如果两个文件有同一个字段,优先使用外部配置文件的字段
4.4 typeAliases(类型别名)
- 为java类型设置一个短的名字,比如select标签中的
com.infinite.pojo.User
需要一个简洁一点的别名 - 该配置存在的意义仅在于减少类完全限定名的冗余
<!-- 为类起别名-->
<typeAliases>
<typeAlias type="com.infinite.pojo.User" alias="User"/>
</typeAliases>
还可以指定一个包名
,MyBatis会在包名下面搜索需要的JavaBean,如:
<typeAliases>
<package name="com.infinite.pojo"/>
</typeAliases>
此时可以扫描实体类的包,它的默认名就是这个类的类名(首字母改小写)。
在实体类比较少的时候,可以使用第一种方法。
在实体类很多的时候。可以使用第二种方法
第一种可以自起别名。
第二种不能直接起别名,可以在目标类上添加@alias("别名")
注解实现
@alias("user")
public class User{}
4.5 settings(设置)
常用的选项
设置名 | 描述 | 有效值 | 默认值 |
---|---|---|---|
cacheEnabled | 全局性地开启或关闭所有映射器配置文件中已配置的任何缓存。 | true | false | true |
lazyLoadingEnabled | 延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。 特定关联关系中可通过设置 fetchType 属性来覆盖该项的开关状态。 | true | false | false |
logImpl | 指定 MyBatis 所用日志的具体实现,未指定时将自动查找。 | (见官方文档) | 未设置 |
使用例
<settings>
<setting name="cacheEnabled" value="true"/>
<setting name="lazyLoadingEnabled" value="true"/>
</settings>
4.6 其他配置
- typeHanders(类型处理器)
- objectFactory(对象工厂)
- plugins插件
- mybatis-generator-core
- mybatis-plus
- 通用mapper
4.7 mappers(映射器)
MapperRegistry
:注册绑定Mapper文件
方法一
<mappers>
<mapper resource="com/infinite/dao/UserMapper.xml"/>
</mappers>
方法二 使用class绑定注册
<mappers>
<!--这里将UserDao改名为UserMapper,否则运行会出错-->
<mapper resource="com.infinite.dao.UserMapper"/>
</mappers>
这种方法下:接口和它的Mapper配置文件必须同名,且在同一个包下。
方法三 使用扫描包进行注入绑定
<mappers>
<!--这里将UserDao改名为UserMapper,否则运行会出错-->
<package resource="com.infinite.dao"/>
</mappers>
这种方法下:接口和它的Mapper配置文件必须同名,且在同一个包下。
4.8 生命周期和作用域
生命周期和作用域若使用错误,会导致非常严重的并发问题。
SqlSessionFactoryBuilder:
- 一旦创建了SqlSessionFactory,就不需要它了
- 局部变量
SqlSessionFactory:
- 类似于数据库连接池
- SqlSessionFactory一旦背创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建另一个实例。
- SqlSessionFactory的最佳作用域时应用作用域
- 最简单的就是单例模式或者静态单例模式
SqlSession:
- 连接到数据库连接池的一个请求
- SqlSession的实例不是线程安全的,因此时不能被共享的,所以它的最佳作用域是请求或方法作用域
- 使用完毕后要关闭,以免资源被占用
每一个Mapper即一项业务
5、解决属性名和字段名不一致的问题
5.1 问题
数据库中的字段
新建一个项目(内容拷贝第三部分的项目),
User
public class User {
private int id;
private String name;
private String password; //属性名和数据库中的字段名不一致
// some method
}
测试
@Test
public void getUserById(){
//获取SqlSession对象
SqlSession sqlSession=MyBatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user=mapper.getUserById(2);
System.out.println(user.toString());
sqlSession.close();
}
测试效果
select * from mybatis.user where id=#{id};
换种写法就是
select id,name,pwd from mybatis.user where id=#{id};
此时数据库并没有在对象中找到 pwd属性
5.2 解决方式
方法一:起别名
一种方式是将配置文件中的查询语句改成如下
<select id="getUserById" parameterType="int" resultType="com.infinite.pojo.User">
select id,name,pwd as password from mybatis.user where id=#{id};
</select>
方法二:resultMap(结果集映射)
<!-- 结果集映射-->
<!-- column对应数据库中的字段,property对应实体集中的属性-->
<resultMap id="UserMap" type="User">
<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>
resultMap
元素是 MyBatis 中最重要最强大的元素。- resultMap 的设计思想是,对简单的语句做到零配置,对于复杂一点的语句,只需要描述语句之间的关系就行了。
- resultMap最优秀的地方在于,虽然你已经很了解它,但是根本就不需要显示地用到它们。
6、日志
6.1 日志工厂
目的:出现异常时便于排错
之前出现bug时,可以通过sout、debug检查错误。
现在可以用日志工厂实现
- SLF4J
- LOG4J [掌握]
- LOG4J2
- JDK_LOGGING
- COMMONS_LOGGING
- STDOUT_LOGGING [掌握]
- NO_LOGGING
STDOUT_LOGGING :标准日志输出
mybatis-config.xml
<!-- 设置选项-->
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
测试
@Test
public void getUserById(){
//获取SqlSession对象
SqlSession sqlSession=MyBatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user=mapper.getUserById(2);
System.out.println(user.toString());
sqlSession.close();
}
测试输出
6.2 LOG4J
- Log4j是Apache的一个开源项目,通过使用Log4j,我们可以控制日志信息输送的目的地是控制台、文件、GUI组件
- 我们也可以控制每一条日志的输出格式
- 可以定义每一条日志信息的级别,我们能够更加细致地控制日志的生成过程
- 可以通过一个配置文件来灵活地进行配置,而不需要修改应用的代码
-
导包
pom.xml
<!-- https://mvnrepository.com/artifact/log4j/log4j --> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency>
-
log4j配置文件
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/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
-
配置log4j为日志的实现
mybatis-config.xml
<settings> <settings> <setting name="logImpl" value="LOG4J"/> </settings> </settings>
-
测试查询
使用例
-
在要使用log4j的类中,导入包
import org.apache.log4j.Logger
-
日志对象,参数为当前类的class
static Logger logger=Logger.getLogger(UserMapperTest.class);
-
日志级别
logger.info("info:进入了testlog4j"); logger.debug("debug:进入了testlog4j"); logger.error("error:进入了testlog4j");
-
测试
7、分页
目的:减少数据处理量
7.1 普通使用例
-
接口
//分页查询 List<User> getUserByLimit(Map<String,Integer> map);
-
UserMapper.xml
<!-- 分页查询--> <select id="getUserByLimit" parameterType="map" resultMap=""> select * from mybatis.user limit #{startIndex},#{pageSize}; </select>
-
测试
@Test public void getUserByLimit(){ SqlSession sqlSession=MyBatisUtils.getSqlSession(); UserMapper mapper = sqlSession.getMapper(UserMapper.class); HashMap<String, Integer> map = new HashMap<>(); map.put("startIndex",0); map.put("pageSize",2); List<User> userList = mapper.getUserByLimit(map); for (User user : userList) { System.out.println(user.toString()); } sqlSession.close(); }
7.2 RowBounds分页
不使用SQL实现分页
-
接口
//分页查询2 List<User> getUserByRowBounds();
-
UserMapper.xml
<!-- 分页查询2--> <select id="getUserByRowBounds" resultMap="UserMap"> select * from mybatis.user ; </select>
-
测试
@Test public void getUserByRowBounds() { SqlSession sqlSession=MyBatisUtils.getSqlSession(); UserMapper mapper = sqlSession.getMapper(UserMapper.class); //使用RowBounds实现 RowBounds rowBounds = new RowBounds(1, 2); //通过java代码层实现分页 List<User> userList = sqlSession.selectList("com.infinite.dao.UserMapper.getUserByRowBounds",null,rowBounds); for (User user : userList) { System.out.println(user.toString()); } sqlSession.close(); }
7.3 分页插件
Mybatis分页插件 PageHelper
:https://pagehelper.github.io/
具体使用方法直接看官方文档。
8、使用注解开发
8.1 面向接口编程含义
在一个面向对象的系统中,系统的各种功能是由许许多多的不同对象协作完成的。在这种情况下,各个对象内部是如何实现自己的,对系统设计人员来讲就不那么重要了;而各个对象之间的协作关系则成为系统设计的关键。小到不同类之间的通信,大到各模块之间的交互,在系统设计之初都是要着重考虑的,这也是系统设计的主要工作内容。面向接口编程就是指按照这种思想来编程。
关于接口的理解:
- 接口从更深层次的理解,应是定义(规范,约束)与实现(名实分离的原则)的分离
- 接口的本身反映了系统设计人员对系统的抽象理解
- 接口应有两类:第一类是对一个个体的抽象,它可对应为一个抽象体(abstract class)。第二类是对一个个体某一方面的抽象,即形成一个抽象面(interface)
- 一个体有可能有多个抽象面
- 抽象体与抽象面是有区别的
8.2 使用注解开发
-
注解在接口上实现
@Select("select * from mybatis.user") List<User> gerUsers();
-
在核心配置文件中绑定接口
mybatis-config.xml
<!--绑定接口--> <mappers> <mapper class="com.infinite.dao.UserMapper"/> </mappers>
-
测试
@Test public void test(){ SqlSession sqlSession = MyBatisUtils.getSqlSession(); //底层通过反射实现 UserMapper mapper = sqlSession.getMapper(UserMapper.class); List<User> userList = mapper.gerUsers(); for (User user : userList) { System.out.println(user.toString()); } sqlSession.close(); }
注解开发的本质:反射机制
底层:动态代理
mybatis运行流程回顾
8.3 CRUD
可以在工具类创建的时候实现自动提交事务。
public static SqlSession getSqlSession(){
return sqlSessionFactory.openSession(true);
}
编写接口,增加注解
public interface UserMapper{
@Select("select * from user")
List<User> getUsers();
//方法中若存在多个参数,所有的参数钱必须加上 @Param("映射参数名")注解
@Select("select * from user where id=#{mapid};")
User getUserByID(@Param("mapid") int id);
@Insert("insert into user(id,name,pwd) values(#{id},#{name},#{pwd});")
int addUser(User user);
@Update("update user set name=#{name},pwd=#{password} where id=#{id};")
int updateUser(User user);
@Delete("delete from user where id=#{uid};")
int deleteUser(@Param("uid") int id);
}
测试类的写法没有变化
注:必须要把接口注册绑定到核心配置文件中
<mappers>
<mapper class="com.infinite.dao.UserMapper"/>
<!--通配符匹配-->
<mapper class="com/infinite/dao/*Mapper.xml"/>
</mappers>
关于@Param()注解
- 基本类型的参数或者String类型,需要加上这个注解
- 引用类型不需要加
- 如果只有一个基本类型的话,可以不加,但是一般都建议加上
- 在SQL中引用的就是@Param()中设定的属性名
9、Lombok
lombok可以通过简单的注解的形式来帮助我们简化和消除一些必须有但显得很臃肿的Java代码,比如常见的Getter&Setter、toString()、构造函数
等等。lombok不仅方便编写,同时也让我们的代码更简洁。lombok提供了一个功能完整的jar包,可以很方便的与我们的项目进行集成。
https://projectlombok.org
- java library
- plungs
- build tools
- with one annotation your class
使用步骤:
-
在IDEA中安装lombok插件
-
在项目中导入lombok依赖
<dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.16.20</version> </dependency>
-
在实体类上添加需要的注解即可
常用注解
@Getter and @Setter
@FieldNameConstants
@ToString
@EqualsAndHashCode
@AllArgsConstructor, @RequiredArgsConstructor and @NoArgsConstructor
@Log, @Log4j, @Log4j2, @Slf4j, @XSlf4j, @CommonsLog, @JBossLog, @Flogger, @CustomLog
@Data -->自动生成无参构造、getter、setter、tostring、hashcode、equal
@Builder
@SuperBuilder
@Singular
@Delegate
@Value
@Accessors
@Wither
虽然比较简介,但是最好视情况用。过度使用有可能会导致可读性降低
10、多对一处理
案例:
- 多个学生,对应一个老师
- 对于学生而言,多个学生可以关联一个老师 多对一
- 对于老师而言,一个老师可以有多个学生 一对多
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');
搭建测试环境
- 导入lombok
- 新建实体类 Teacher、Student
- 建立Mapper接口
- 建立Mapper.xml文件
- 在核心配置文件中注册Mapper接口或配置文件
- 测试查询是否成功
10.1 按照查询嵌套处理
<!-- 思路:
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-->
<association property="teacher" column="tid" javaType="Teacher" select="getTeacher"/>
</resultMap>
<select id="getTeacher" resultType="Teacher">
select * from teacher where id=#{id};
</select>
测试
@Test
public void testStudent(){
SqlSession sqlSession= MyBatisUtils.getSqlSession();
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
List<Student> students = mapper.getStudent();
for (Student student : students) {
System.out.println(student);
}
sqlSession.close();
}
10.2 按照结果嵌套处理
<!--按照结果嵌套处理-->
<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="id"/>
<result property="name" column="name"/>
<association property="teacher" javaType="Teacher">
<result property="name" column="tname"/>
</association>
</resultMap>
Mysql多对一查询方式:
- 子查询
- 联表查询
11、一对多
一个老师关联多个学生
学生类
@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;
}
需求:获取指定老师下的所有学生信息,以及该老师的信息
Teacher getTeacher(@Param("tid") int id);
方法一:按结果集处理
<!-- 按结果嵌套查询-->
<select id="getTeacher" resultMap="TeacherStudent">
select s.id sid, s.name sname, t.name tname, t.id tid
from student s,teacher t
where t.id=#{tid} and s.tid=t.id;
</select>
<!-- 复杂的属性,需要单一处理 对象:association 集合:collection
javaType 指定属性的类型
对于集合中的泛型信息,需要使用ofType-->
<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>
方法二:按查询嵌套处理
<select id="getTeacher2" resultMap="TeacherStudent2">
select * from mybatis.teacher where id=#{tid};
</select>
<!--这里的select为子查询 column值为teacher表中的一个字段名-->
<resultMap id="TeacherStudent2" type="Teacher">
<collection property="students" javaType="ArrayList" ofType="Student"
select="getStudentByTeacherId" column="id"/>
</resultMap>
<select id="getStudentByTeacherId" resultType="Student">
select * from mybatis.student where tid=#{tid};
</select>
小结
- 关联-association 多对一
- 集合-collection 一对多
- javaType 和 ofType
- javaType 用来指定实体类中属性的类型
- ofType用来指定映射到List或集合中的pojo类型,泛型中的约束类型
注:
- 确保SQL的可读性
- 注意一对多和多对一中,属性名和字段名的问题
- 善用日志排查错误
12、动态SQL
目的:根据不同的条件生成不同的SQL语句
如果你之前用过 JSTL 或任何基于类 XML 语言的文本处理器,你对动态 SQL 元素可能会感觉似曾相识。在 MyBatis 之前的版本中,需要花时间了解大量的元素。借助功能强大的基于 OGNL 的表达式,MyBatis 3 替换了之前的大部分元素,大大精简了元素种类,现在要学习的元素种类比原来的一半还要少。
if
choose (when, otherwise)
trim (where, set)
foreach
搭环境
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;
-
导包
-
编写配置文件
-
编写实体类
@Data public class Blog { private String id; private String title; private String author; private Date createTime; //此处和数据库中的相关字段不同名 private int views; }
-
编写实体类对于的Mapper接口和Mapper.xml文件
数据库插值
BlogMapper.java
int addBlog(Blog blog);
BlogMapper.xml
<insert id="addBlog" parameterType="Blog">
insert into mybatis.blog(id,title,author,create_time,views)
values (#{id},#{title},#{author},#{createTime},#{views});
</insert>
test
@Test
public void addInitBlog(){
SqlSession sqlSession = MyBatisUtils.getSqlSession();
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
Blog blog = new Blog();
blog.setId(IDutils.getId());
blog.setTitle("Mybatis");
blog.setAuthor("狂神说");
blog.setCreateTime(new Date());
blog.setViews(9999);
mapper.addBlog(blog);
blog.setId(IDutils.getId());
blog.setTitle("Java");
mapper.addBlog(blog);
blog.setId(IDutils.getId());
blog.setTitle("Spring");
mapper.addBlog(blog);
blog.setId(IDutils.getId());
blog.setTitle("微服务");
mapper.addBlog(blog);
sqlSession.commit();
sqlSession.close();
}
IF
BlogMapper.java
//查询博客
List<Blog> queryBlogIF(Map map);
BlogMapper.xml
<select id="queryBlogIF" parameterType="map" resultType="Blog">
select * from mybatis.blog where 1=1
<if test="title!=null"> <!--如果title非空,则添加以下语句-->
and title=#{title}
</if>
<if test="author!=null"> <!--如果author非空,则添加以下语句-->
and author=#{author}
</if>
</select>
test
@Test
public void queryBlogIF(){
SqlSession sqlSession = MyBatisUtils.getSqlSession();
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
HashMap map = new HashMap();
map.put("title","Java");
List<Blog> blogs=mapper.queryBlogIF(map);
for (Blog blog : blogs) {
System.out.println(blog);
}
sqlSession.close();
}
choose、when、otherwise
类似于switch-case语句
BlogMapper.java
//查询博客(choose)
List<Blog> queryBlogChoose(Map map);
BlogMapper.xml
<select id="queryBlogChoose" parameterType="map" resultType="Blog">
select * from mybatis.blog
<where>
<!--从上往下一一对比,一发现符合条件的when标签,就添加其中的语句并跳出。
所有when都不匹配,就执行otherwise-->
<choose>
<when test="title!=null">
title=#{title}
</when>
<when test="author!=null">
and author=#{author}
</when>
<otherwise>
and views=#{views}
</otherwise>
</choose>
</where>
</select>
test
@Test
public void queryBlogIF(){
SqlSession sqlSession = MyBatisUtils.getSqlSession();
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
HashMap map = new HashMap();
map.put("title","Java");
map.put("values",9999);
List<Blog> blogs=mapper.queryBlogChoose(map);
for (Blog blog : blogs) {
System.out.println(blog);
}
sqlSession.close();
}
where
对于以下查询
<select id="queryBlogIF" parameterType="map" resultType="Blog">
select * from mybatis.blog where
<if test="title!=null"> <!--如果title非空,则添加以下语句-->
title=#{title}
</if>
<if test="author!=null"> <!--如果author非空,则添加以下语句-->
and author=#{author}
</if>
</select>
如果满足第二个if,则会生成错误的SQL语句
select * from mybatis.blog where and author=#{author}
通过嵌套where标签,可以让框架自动处理多余的and
<select id="queryBlogIF" parameterType="map" resultType="Blog">
select * from mybatis.blog
<where>
<if test="title!=null">
title=#{title}
</if>
<if test="author!=null">
and author=#{author}
</if>
</where>
</select>
set
set标签会自动添加SET并处理标签中 ,
的问题
<update id="updateBlog" parameterType="map">
update mybatis.blog
<set>
<if test="title!=null">
title=#{title}, <!--会视情况处理末尾的逗号-->
</if>
<if test="author!=null">
author=#{author}
</if>
</set>
where id=#{id}
</update>
trim
mybatis的trim标签一般用于 去除sql语句中多余的and关键字,逗号,或者给sql语句前拼接 “where“、“set“以及“values(“ 等前缀,或者添加“)“等后缀。
可用于选择性插入、更新、删除或者条件查询等操作。
不常用。可参考博客:https://blog.csdn.net/wt_better/article/details/80992014
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 mybatis.blog
<where>
<include refid="if-title-author"></include>
</where>
</select>
注,使用这个标签时:
- 最好基于单表来定义SQL片段
- 不要在sql标签中写where标签
Foreach
BlogMapper.java
//查询第1,2,3号记录的博客
List<Blog> queryBlogForeach(Map map);
BlogMapper.xml
<select id="queryBlogForeach" parameterType="map" resultType="map">
select * from mybatis.blog
<where>
<!--原查询语句:select * from mybatis.blog where 1=1 and (id=1 or id=2 or id=3) -->
<!--collections为map中的一个key,定义为ids,测试的时候要对应这个名字
open代表开始位置,close代表结束位置,separator为分隔符。这样可以将括号中的3个id遍历一次-->
<foreach collection="ids" item="id" open="and (" close=")" separator="or">
id=#{id}
</foreach>
</where>
</select>
test
@Test
public void queryBlogForeach(){
SqlSession sqlSession = MyBatisUtils.getSqlSession();
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
HashMap map = new HashMap();
ArrayList<Integer> ids=new ArrayList<Integer>();
ids.add(1);
ids.add(2);
ids.add(3);
map.put("ids",ids);
List<Blog> blogs = mapper.queryBlogForeach(map);
// for (Blog blog : blogs) {
// System.out.println(blog);
// }
System.out.println(blogs);
sqlSession.close();
}
动态SQL就是在拼接SQL语句,只要保证SQL的正确性,按照SQL的格式,去排列组合即可。
可以在Mysql中先写一个完整的SQL,然后再对应地去修改成为我们地动态SQL实现通用即可。
13、缓存(了解)
13.1 简介
第一次查询-->连接数据库。
第二次若还是连接数据库,会比较耗资源。
可以将第一次查询的结果放入缓存,第二次查询时,可以直接查缓存
- 什么是缓存[Cache]
- 存在内存中的临时数据
- 将用户经常查询的数据放在缓存(内存)中,用户去查询数据就不用从磁盘上(关系型数据库数据文件)查询,从缓存中查询,从而提高查询效率,解决了高并发系统的性能问题
- 为什么使用缓存
- jia年少和数据库的交互次数,减少系统开销,提高系统效率
- 什么样地数据能使用缓存
- 经常查询并且不经常改变的数据
13.2 Mybatis缓存
- Mybatis包含一个非常强大的查询缓存的特性,它可以非常方便地定制和配置缓存。缓存可以极大提升查询效率
- Mybatis系统中默认定义了两级缓存:一级缓存和二级缓存
- 默认情况下,只有一级缓存开启。 (SqlSession级别的缓存,也称为本地缓存)
- 二级缓存需要手动开启和配置,他时基于namespace级别的缓存
- 为了提高扩展性,Mybatis定义了缓存接口Cache,可以通过实现Cache接口来自定义二级缓存
13.3 一级缓存
一级缓存又称本地缓存 如SqlSession
- 与数据库同义词会话期间查询到的数据会放在本地缓存中
- 以后如果需要获取相同的数据,直接从缓存中拿,没必要再去查询数据库
测试步骤:
- 核心配置文件中开启日志
- 测试zai一个Session中查询两次相同记录
- 查看日志输出
测试例
User.java
@Data
public class User {
private int id;
private String name;
private String pwd;
}
UserMapper.java
public interface UserMapper {
//根据id查询用户
User queryUserById(@Param("id") int id);
}
UserMapper.xml
<select id="queryUserById" resultType="User">
select * from user where id=#{id}
</select>
test1
@Test
public void test(){
SqlSession sqlSession = MyBatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user=mapper.queryUserById(1);
System.out.println(user);
System.out.println("=========================");
User user2=mapper.queryUserById(1); //查询同一条记录
System.out.println(user2);
sqlSession.close();
}
test2
public void test(){
SqlSession sqlSession = MyBatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user=mapper.queryUserById(1);
System.out.println(user);
System.out.println("=========================");
User user2=mapper.queryUserById(2); //查询不同的记录
System.out.println(user2);
sqlSession.close();
}
缓存失效的情况
-
查询不同的东西
-
增删改操作,可能会改变原来的数据,从而刷新缓存
-
查询不同的Mapper.xml
-
手动清理缓存
sqlSession.clearCache(); //手动清理缓存
一级缓存默认开启,在SqlSession的一个开启到关闭的周期中有效。
一级缓存类似于map
13.4 二级缓存
- 二级缓存又称全局缓存,一级缓存作用域很低,所以诞生了二级缓存
- 基于namespace级别的缓存,一个名称空间,对应一个二级缓存
- 工作机制
- 一个会话查询一条数据,这个数据就会被放在当前会话的一级缓存中
- 如果当前会话关闭了,这个会话对应的一级缓存就没了,但是我们想要的时,会话关闭了,一级缓存中的数据被保存到二级缓存中。
- 新的会话查询消息,就可以从二级缓存中获取内容
- 不同的mapper查出的数据会放在自己对应的缓存(map)中
在mapper.xml
添加一个标签即可开启二级缓存
<cache/>
使用例
-
开启全局缓存
mybatis-config.xml
<settings> <setting name="logImpl" value="STDOUT_LOGGING"/> <!-- 显式地开启全局缓存--> <setting name="cacheEnabled " value="true"/> </settings>
-
在要使用二级缓存的mapper.xml中开启标签
<cache/>
这个标签有几个可调参数
<cache eviction="FIFO" <--缓存替换策略 flushInterval="60000" <--缓存刷新间隔 size="$12" readOnly="true"/>
-
测试
@Test public void test(){ //两个Session有自己的一级缓存 //一般来说,这两个缓存不会共享 //开启二级缓存后,这两个session的查询结果会共享 //这样,即使关闭了一个session,另一个session仍然可以获得二级缓存中的共享内容 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(); System.out.println("============================"); UserMapper mapper2 = sqlSession2.getMapper(UserMapper.class); User user2=mapper2.queryUserById(1); System.out.println(user2); sqlSession2.close(); }
注:实体类要序列化,否则会报错
@Data
public class User implements Serializable {
private int id;
private String name;
private String pwd;
}
小结:
- 开启的二级缓存,在同一个Mapper.xml下有效
- 所有的数据都会先放在一级缓存中
- 只有当会话提交,或者关闭的时候,才会提交到二级缓存中
13.5 缓存管理
13.6 自定义缓存-ehcache
ehcache是一种广泛使用的开源Java分布式缓存。主要面向通用缓存
<!-- 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>
在mapper.xml的cache标签中指定自定义缓存类
<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>
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>
以后会使用Redis缓存