mybatis 笔记
mybatis
前提 环境
- JDK 1.8
- mysql 5.7
- maven 3.6.3
- idea
回顾:
- JDBC
- MYSQL
- JAVA基础
- Maven
- Junit
SSM 框架:配置文件的。 看官方文档
1、 简介
1.1、 什么是mybatis
- MyBatis 是一款优秀的持久层框架,
- 它支持自定义 SQL、存储过程以及高级映射。
- MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。
如何获取mybatis?
-
maven仓库 —用的人最多的
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.4.6</version> </dependency>
-
github: mybatis-3/src/site at master · mybatis/mybatis-3 · GitHub
1.2、持久层
数据持久化
-
持久化就是将程序的数据在持久状态和瞬时状态的转化的过程
-
内存:断电即失
-
数据库(JDBC), io文件持久化
-
生活 冷藏 罐头 保鲜膜 … …
为什么要持久化
- 有一些对象,不能让他丢掉。
- 内存太贵
1.3、 持久层
Dao层,Service层,Controller层…
- 完成持久化工作的代码块
- 层界限十分明显
1.4、为什么需要Mybatis?
-
帮助程序员将数据存入到数据库中。
-
方便
-
传统的JDBC代码太复杂。简化。框架。自动化。
-
不用mybatis也可以。 但是Mybatis容易上手。技术没有高低之分
-
优点
- 简单易学:本身就很小且简单。没有任何第三方依赖,最简单安装只要两个jar文件+配置几个sql映射文件。易于学习,易于使用。通过文档和源代码,可以比较完全的掌握它的设计思路和实现。
- 灵活:mybatis不会对应用程序或者数据库的现有设计强加任何影响。 sql写在xml里,便于统一管理和优化。通过sql语句可以满足操作数据库的所有需求。
- 解除sql与程序代码的耦合:通过提供DAO层,将业务逻辑和数据访问逻辑分离,使系统的设计更清晰,更易维护,更易单元测试。sql和代码的分离,提高了可维护性。
- 提供映射标签,支持对象与数据库的ORM字段关系映射。
- 提供对象关系映射标签,支持对象关系组建维护。
- 提供xml标签,支持编写动态sql。
最总要的一点:使用的人多
Spring SpringMVC SpringBoot
2、 第一个mybatis程序
思路: 搭建环境–>导入Mybatis–>编写代码–>测试!
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,'小王','123789'),
(3,'大明','456789')
新建项目
-
新建一个普通的maven项目
-
删除src
-
导入maven依赖
<?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>lmq.com</groupId> <artifactId>demo01</artifactId> <version>1.0-SNAPSHOT</version> <properties> <maven.compiler.source>8</maven.compiler.source> <maven.compiler.target>8</maven.compiler.target> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> <dependencies> <!-- mysql--> <!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.28</version> </dependency> <!-- mybatis --> <!-- https://mvnrepository.com/artifact/org.mybatis/mybatis --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.4.6</version> </dependency> <!-- junit --> <!-- https://mvnrepository.com/artifact/junit/junit --> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12-beta-3</version> <scope>test</scope> </dependency> </dependencies> </project>
2.2、创建一个模块
-
编写mybatis的核心配置文件
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "https://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://124.71.168.125:3310/mybatis?useSSL=true&userUnicode=true&characterEncoding=UTF-8"/> <property name="username" value="root"/> <property name="password" value="123456"/> </dataSource> </environment> </environments> </configuration>
-
编写mybatis工具库
package lmq.com.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; /** * @author 羡鱼 * @version 1.0 * @date 2023/6/12 21:18 */ //sqlSessionFactory --> sqlSession public class mybatisUtils { private static SqlSessionFactory sqlSessionFactory; // 提升作用域 static{ try { String resource = "org/mybatis/example/mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); }catch (IOException e){ e.printStackTrace(); } } public static SqlSession getSqlSession(){ return sqlSessionFactory.openSession(); } }
2.3、 编写代码
-
实体类
-
Dao接口
package lmq.com.dao; import lmq.com.pojo.User; import java.util.List; /** * @author 羡鱼 * @version 1.0 * @date 2023/6/12 21:30 */ public interface UserDao { //专业名称 Mapper List<User> getUserList(); }
-
接口实现类 由原来的UserDaoImp 转变为一个Mapper配置文件
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "https://mybatis.org/dtd/mybatis-3-mapper.dtd"> <!-- namespace=绑定一个对应的Dao/Mapper接口 --> <mapper namespace="lmq.com.dao.UserDao"> <!-- select 查询语句 id 是原来的接口名字 --> <select id="getUserList" resultType="lmq.com.pojo.User"> select * from mybatis.user </select> </mapper>
2.4、 测试
注意点:
org.apache.ibatis.binding.BindingException: Type interface lmq.com.dao.UserDao is not known to the MapperRegistry.
MapperRegistry是什么?
在核心文件中注册mappers
mybatis-config.xml
<!-- 每一个Mapper.xml都需要在mybatis核心配置文件中注册 -->
<mappers>
<mapper resource="lmq/com/dao/UserMapper.xml"/>
</mappers>
注意2
java.lang.ExceptionInInitializerError
''''''
Caused by: org.apache.ibatis.exceptions.PersistenceException:
### Error building SqlSession.
### The error may exist in lmq/com/dao/UserMapper.xml
maven 约定大于配置,我们之后可能遇到我们写的配置文件,无法被到出或者生效的问题,解决方法 maven
建议父子项目都放 以免父项目不生效
<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>
-
junit测试
package lmq.com.dao; import lmq.com.pojo.User; import lmq.com.utils.MybatisUtils; import org.apache.ibatis.session.SqlSession; import org.junit.Test; import java.util.List; /** * @author 羡鱼 * @version 1.0 * @date 2023/6/12 22:16 */ public class UserDaoTest { @Test public void test(){ // 获得sqlSersion 对象 SqlSession sqlSession = MybatisUtils.getSqlSession(); //方法一:getMapper 面相接口编程 UserDao userDao = sqlSession.getMapper(UserDao.class); List<User> userList = userDao.getUserList(); for (User user:userList){ System.out.println(user); } // 关闭sqlSession sqlSession.close(); } }
常见报错:
- 配置文件没有注册
- 绑定接口错误。
- 方法名不对
- 返回类型不对
- Maven导出资源
小结:
- 写一个工具类
- 写mybatis配置文件
- 写实体类
- 写接口
- 写maper.xml
- 写测试文件
- Maven过滤问题 - -如果加了就忽略
3、CRUD
1、 namespace 命名空间
namespace 包名和要的Dao/mapper 接口的包名一致。
2、 select
选择,查询过语句;
-
id: 就是对应namespase对应的方法名
-
resulType: Sql语句执行的返回值!
-
parameterType: 参数类型
-
编写接口
List<User> getUserList();
-
编写对应的mapper中的sql语句
<select id="getUserList" resultType="lmq.com.pojo.User"> select * from mybatis.user </select>
-
测试
@Test public void test(){ // 获得sqlSersion 对象 SqlSession sqlSession = MybatisUtils.getSqlSession(); //方法一:getMapper 面相接口编程 UserMapper userDao = sqlSession.getMapper(UserMapper.class); List<User> userList = userDao.getUserList(); // 方法二: 不推荐 // List<User> userList1 = sqlSession.selectList("lmq.com.dao.UserDao.getUserList"); // List<User> userList2 = sqlSession.selectOne("lmq.com.dao.UserDao.getUserList"); // List<User> userList3 = sqlSession.selectMap("lmq.com.dao.UserDao.getUserList"); for (User user:userList){ System.out.println(user); } // 关闭sqlSession sqlSession.close(); }
3、 Insert
4、update
5、 Delete
-
编写接口
// insert 一个用户 int addUser(User user); // update 修改用户 int updateUser(User user); // delete int deleteUser(int id);
-
编写对应的mapper中的sql语句
<insert id="addUser" parameterType="lmq.com.pojo.User"> insert into mybatis.user (id,name,pwd) values (#{id},#{name},#{pwd}) </insert> <update id="updateUser" parameterType="lmq.com.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 public void addUser(){ SqlSession sqlSession = MybatisUtils.getSqlSession(); UserMapper mapper = sqlSession.getMapper(UserMapper.class); // 面相对象编程 int res = mapper.addUser(new User(4, "小王", "123456")); System.out.println(res); // 事务提交 保持原子的一致性 sqlSession.commit(); sqlSession.close(); } @Test public void updateUser(){ SqlSession sqlSession = MybatisUtils.getSqlSession(); UserMapper mapper = sqlSession.getMapper(UserMapper.class); mapper.updateUser(new User(4,"呵呵","123123")); // 切记提交事务 sqlSession.commit(); sqlSession.close(); } @Test public void deleteUser(){ SqlSession sqlSession = MybatisUtils.getSqlSession(); UserMapper mapper = sqlSession.getMapper(UserMapper.class); int i = mapper.deleteUser(4); System.out.println(i); // 切记提交事务 sqlSession.commit(); sqlSession.close(); }
注意: 增删改是事务 原子的一致性 所以要commit() 提交
6、错误分析
- 标签不要匹配错
- resource 绑定 Mapper 要用路径
- 程序配置文件必须符合规范
- NullPointerException 没有注册到资源
- 输出的xml文件存在乱码问题
- Maven资源没有导出问题
7、万能的Map
假设,我们的实体类,或者数据库中的表,字段或者参数过多,我们应当考虑用Map!
// insert 一个用户 万能的map
int addUser2(Map<String,Object> map);
<insert id="addUser2" parameterType="map">
insert into mybatis.user(id, name, pwd)
values (#{userid},#{username},#{password});
</insert>
// 万能的map
@Test
public void addUser2() {
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
HashMap<String, Object> map = new HashMap<String, Object>();
map.put("userid", 5);
map.put("username", "小强");
map.put("password", "123789");
mapper.addUser2(map);
sqlSession.commit();
sqlSession.close();
}
Map传递参数,直接诶在Sql中取出key即可! 【parameterType=“map”】
对象传递参数,直接在sql中取对象的属性即可! 【parameterType=“Object”】
只有一个基本类型参数的情况下,可以直接在sql中取到
多个参数使用Map,或者注解
8、模糊查询
模糊查询这么写?
-
Java代码执行的时候,传递通配符%%
map.put("value","%小%");
-
在sql凭借中使用通配符!
select * from mybatis.user where name like "%"#{value}"%"
4、配置解析
1、核心配置文件
-
mybatis-config.xml
-
MyBatis 的配置文件包含了会深深影响Mabatis行为的设置和属性信
configuration(配置) properties(属性) settings(设置) typeAliases(类型别名) typeHandlers(类型处理器) objectFactory(对象工厂) plugins(插件) environments(环境配置) environment(环境变量) transactionManager(事务管理器) dataSource(数据源) databaseIdProvider(数据库厂商标识) mappers(映射器)
2、环境配置(environments)
MyBatis可以配置成使用多种环境
不过要记住:尽管可以配置多个环境,但是每个SqlSessionFactory实例只能选择一种环境。
学会使用配置多套运行环境!
MyBatis默认的事务管理容器就是JDBC,连接处:POOLED
3、属性(properties)
我们可以通过properties属性来实现引用配置文件
这些属性可以在外部进行配置,并可以进行动态替换。你既可以在典型的 Java 属性文件中配置这些属性,也可以在 properties 元素的子元素中设置 【db.properties】
编写一个配置文件
db.properties
driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://124.71.168.125:3310/mybatis?useSSL=true&userUnicode=true&characterEncoding=UTF-8
username=root
password=123456
在核心配置文件内引入
<!-- 引入外部配置文件 -->
<properties resource="db.properties"/>
<!-- 外部引入一半 里面写一半 优先使用外部配置文件-->
<properties resource="db2.properties">
<property name="username" value="root"/>
<property name="password" value="123456"/>
</properties>
<environments default="yr">
<!-- 第一套环境 -->
<!--引入写法-->
<environment id="yr">
<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>
- 可以直接引入外部文件
- 可以在其中增加一些属性配置
- 如果有相同字段优先使用外部配置文件
xml标签有顺序
The content of element type "configuration" must match "(properties?,settings?,typeAliases?,typeHandlers?,objectFactory?,objectWrapperFactory?,reflectorFactory?,plugins?,environments?,databaseIdProvider?,mappers?)".
4、类型别名(typeAliases)
-
类型别名可为 Java 类型设置一个缩写名字。
-
它仅用于 XML 配置,意在降低冗余的全限定类名书写。
<!-- 一个个定义 --> <typeAliases> <typeAlias alias="Author" type="domain.blog.Author"/> <typeAlias alias="Blog" type="domain.blog.Blog"/> <typeAlias alias="Comment" type="domain.blog.Comment"/> <typeAlias alias="Post" type="domain.blog.Post"/> <typeAlias alias="Section" type="domain.blog.Section"/> <typeAlias alias="Tag" type="domain.blog.Tag"/> </typeAliases>
也可以指定一个包,mybatis会在包名下面搜索需要的Java Bean,
扫描实体类的包,它的默认别名就为这个类的 类名,首字母小写
<!-- 设定一个包 --> <typeAliases> <package name="lmq.com.pojo" </typeAliases>
在实体类比较少的时候,使用第一种方式。
如果实体类十分多,建议使用第二种。
第一种可以DIY别名,
第二种则·不可以· 但是可以用注解取别名 别名大于包名 在实体类上加注解
@Alias("author") public class Author { ... }
5、 设置
这是MyBatis中纪委重要的调整设置,它们会改变MyBatis的运行时行为。
设置名 | 描述 | 有效值 | 默认值 |
---|---|---|---|
cacheEnabled | 全局性地开启或关闭所有映射器配置文件中已配置的任何缓存。 | true | false | true |
lazyLoadingEnabled | 延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。 特定关联关系中可通过设置 fetchType 属性来覆盖该项的开关状态。 |
true | false | false |
logImpl | 指定 MyBatis 所用日志的具体实现,未指定时将自动查找。 | SLF4J | LOG4J(3.5.9 起废弃) | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING | 未设置 |
6、其他设置
- typeHandlers(类型处理器)
- objectFactory(对象工厂)
- plugins(插件)
- mybatis-generator-core
- mybatis-plus
- 通用mapper
7、映射器(mappers)
MapperRegistry : 注册绑定我们的Mapper文件
方式一:
<!-- 使用相对于类路径的资源引用 -->
<mappers>
<mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
<mapper resource="org/mybatis/builder/BlogMapper.xml"/>
<mapper resource="org/mybatis/builder/PostMapper.xml"/>
</mappers>
方式二:
<!-- 使用映射器接口实现类的完全限定类名 -->
<mappers>
<mapper class="org.mybatis.builder.AuthorMapper"/>
<mapper class="org.mybatis.builder.BlogMapper"/>
<mapper class="org.mybatis.builder.PostMapper"/>
</mappers>
注意点:
- 接口和它的Mapper配置文件必须同名!
- 接口和他的Mapper配置文件必须在一个包下
方式三:
<!-- 将包内的映射器接口全部注册为映射器 -->
<mappers>
<package name="org.mybatis.builder"/>
</mappers>
注意点:
- 接口和它的Mapper配置文件必须同名!
- 接口和他的Mapper配置文件必须在一个包下
8、生命周期
生命周期和作用域 是至关重要的 因为错误的使用会导致非常严重的并发问题
SqlsessionFactoryBuilder:
- 一但创建了SqlSessionFactory,就不再需要它了
- 局部变量
SqlsessionFactory
- 可以理解为:数据库连接池
- SqlSessionFactory 一旦被创建就应该在应用的运行期间一直存在,没有必要再次创建另一个的必要
- 因此SqlSessionFactory的最佳作用域是应用作用域
- 最简单的就是单例模式或者静态单列模式。
sqlsession
- 链接到连接池的一个请求
- SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域
- 用完之后需要赶紧关闭,否则资源被占用
每个Mapeer 就代表一个具体的业务!
5、解决属性名和字段不一致的问题
数据库中的字段
分析处理器 pwd => password
解决方法 :
-
取别名
select * from mybatis.user where id = #{id} select id,name,pwd as password from mybatis.user where id = #{id
-
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 是原来的接口名字 --> <select id="getUserById" parameterType="int" resultMap="UserMap"> select * from mybatis.user where id = #{id} </select>
-
resultMap
元素是 MyBatis 中最重要最强大的元素。 -
对于复杂一点的语句,只需要描述语句之间的关系就行了。
-
ResultMap
的优秀之处——你完全可以不用显式地配置它们。 虽然上面的例子不用显式配置ResultMap
6、日志
6.1、日志工厂
如果一个数据库操作出现了异常 我们需要拍错 所以日志就是最好的助手
曾经:debug
; sout
现在:日志工厂
logImpl | 指定 MyBatis 所用日志的具体实现,未指定时将自动查找。 | SLF4J | LOG4J(3.5.9 起废弃) | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING | 未设置 |
---|
- SLF4J
- LOG4J(3.5.9 起废弃) 【掌握】
- LOG4J2
- JDK_LOGGING
- COMMONS_LOGGING
STDOUT_LOGGING
【掌握】 标准的日志- NO_LOGGING
在Mybatis中具体使用那个日志实现,在设置中设定
STDOUT_LOGGING 标准日志输出
在mybatis核心配置文件中,配置我们的日志
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
6.2、LOG4J
什么是log4j?
-
Log4j是Apache的一个开源项目,通过使用Log4j,我们可以控制日志信息输送的目的地是控制台、文件、GUI组件,甚至是套接口服务器、NT的事件记录器、UNIX Syslog守护进程等
-
我们也可以控制每一条日志的输出格式
-
通过定义每一条日志信息的级别,我们能够更加细致地控制日志的生成过程
-
最令人感兴趣的就是,这些可以通过一个配置文件来灵活地进行配置,而不需要修改应用的代码。
-
先导入log4j的包
<!-- https://mvnrepository.com/artifact/log4j/log4j --> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency>
乱码问题 [org.apache.ibatis.io.DefaultVFS]-Reader entry: ���� 4
<!-- 加入jboss包 --> <!-- https://mvnrepository.com/artifact/org.jboss/jboss-vfs --> <dependency> <groupId>org.jboss</groupId> <artifactId>jboss-vfs</artifactId> <version>3.2.16.Final</version> </dependency>
-
log4j,properties
#将等级为Debug的日志信息 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/lmq.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为日志的实现
<settings> <setting name="logImpl" value="LOG4J"/> </settings>
-
LOG4J 的使用!直接测试 运行刚才的查询
简单使用
-
在要使用LOG4J的类中,导入
import org.apache.log4j.Logger;
-
日志对象,参数为当前类的class
Logger logger = Logger.getLogger(UserDaoTest.class);
-
日志级别
logger.info("info:进入了testLOG4J"); logger.debug("debug:进入了testLOG4J"); logger.error("error:进入了testLOG4J");
7、分页
思考:为什么分页?
- 减少对数据的处理量
7.1、最初用limit 分页
select * from User limit 0,2;
-- 没页显示的数量
-- 第一个参数是页数 只写一个 就从0到这个
-- 第二个参数是显示数量
-- 特殊用法
-- limit 第二个参数为 -1 原来是一个BUG 后来修复了;
使用Mybatis实现分页核心SQL
-
接口
List<User> getUserByLimit(Map<String,Object> map);
-
Mapper.xml
<select id="getUserByLimit" parameterType="map" resultMap="UserMap"> select * from mybatis.user limit #{startIndex},#{pagesize} </select>
-
测试
@Test public void getUserByLimit(){ SqlSession sqlSession = MybatisUtils.getSqlSession(); UserMapper mapper = sqlSession.getMapper(UserMapper.class); HashMap<String, Object> 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); } sqlSession.close(); }
7.2、RowBounds 类
不再使用Sql实现分页
-
接口
List<User> gerRowbounds();
-
mapper.xml
<select id="gerRowbounds" resultMap="UserMap"> select * from mybatis.user </select>
-
测试
public void gerRowbounds(){ SqlSession sqlSession = MybatisUtils.getSqlSession(); RowBounds rowBounds = new RowBounds(1, 2); // 通过java 代码实现分页 直接到方法 第一个参数 到接口 + 函数 List<User> userList = sqlSession.selectList("lmq.com.dao.UserMapper.gerRowbounds",null,rowBounds); for (User user : userList) { System.out.println(user); } sqlSession.close(); }
7.3,分页插件
了解就可以了 大项目使用 在springboot集成
8、使用注解开发
8.1、面向接口编程
- 为什么选择面相接口编程
- 解耦,可拓展 提高复用 分层开发中上层不用管具体实现,大家都遵守共同标准 让开发变得容易,规范性更好
解耦
- 接口更深次的理解 定义(规范,约束)与实现(名实分离的原则)的分离。
- 接口的本身反应了系统设计人员的抽象理解。
- 接口有两类:
- 第一类是对一个个体的抽象,他可以对应为一个抽象体 (abstract class)
- 第二类对一个个体某一方面的抽象 级抽象面 (interface)
- 一个个体可能有很多个抽象面。抽象体与抽象面是有区别的
三个方面的区别:
- 面向对象是指:我们考虑问题时,以对象为单位,考虑他的属性和方法
- 面相过程是指,我们考虑问题时,以一个具体的流程(事务过程)为单位,考虑他的实现;
- 接口设计与非接口设计是针对复用技术而言的,与面相对象(过程)不是一个问题。更多体现就是对系统整体的架构.
8.2、使用接口开发
-
注解在接口上实现
@Select("select * from mybatis.user") List<User> getUsers();
-
需要在核心配置文件中绑定接口
<mappers> <!-- 绑定接口 --> <mapper class="lmq.com.dao.UserMapper"/> </mappers>
-
测试
import lmq.com.dao.UserMapper; import lmq.com.pojo.User; import lmq.com.utils.MybatisUtils; import org.apache.ibatis.session.SqlSession; import org.junit.Test; import java.util.List; /** * @author 羡鱼 * @version 1.0 * @date 2023/6/18 0:19 */ public class UserMapperTest { @Test public void test(){ SqlSession sqlSession = MybatisUtils.getSqlSession(); UserMapper mapper = sqlSession.getMapper(UserMapper.class); List<User> users = mapper.getUsers(); for (User user : users) { System.out.println(user); } sqlSession.close(); } }
本质:反射机制实现
底层:动态代理
8.3、CRUD
我们可以在工具类创建的时候实现自动提交事务
public static SqlSession getSqlSession() {
// return sqlSessionFactory.openSession();
// 开启自动提交commit
return sqlSessionFactory.openSession(true);
}
接口类编写,增加注解
@Select("select * from user where id = #{id}") //#{id} 取的是@param 的
User getUserById(@Param("id") int id);
// User getUserById(@Param("id") int id,@Param("name") String name);
// Insert
@Insert("insert into user(id,name,pwd) values (#{id},#{name},#{password})")
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 = #{id}")
int deleteUser(@Param("id") int id);
测试类
@Test
public void getUserById(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User users = mapper.getUserById(1);
// for (User user : users) {
System.out.println(users);
// }
sqlSession.close();
}
@Test
public void addUser(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
int i = mapper.adduser(new User(6,"hello","123123"));
if (i>0)
System.out.println("插入成功");
else
System.out.println("插入失败");
}
@Test
public void updateUser(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
int i = mapper.updateUser(new User(6,"tow","66666"));
if (i>0)
System.out.println("修改成功");
else
System.out.println("修改失败");
}
@Test
public void deleteUser(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
int i = mapper.deleteUser(6);
if (i>0)
System.out.println("删除成功");
else
System.out.println("删除失败");
}
【注意:我们必须要将接口绑定到我们的核心配置文件】
关于@Param()注解
- 基本类型的参数或者String类型,需要加上
- 引用类型不需要加
- 如果只有一个基本类型可以不加 但是建议加上 形成良好编程习惯
- 我们的sql引用的就是@param()中设定的属性名
{} $()区别
{} 预编译
${} 没有预编译 容存在sql注入
9、Lombok
Project Lombok is a java library that automatically plugs into your editor and build tools, spicing up your java.
Never write another getter or equals method again, with one annotation your class has a fully featured builder, Automate your logging variables, and much more.
- java library
- plugs
- build tools
- with one annotation your class
使用步骤
-
idea 里面安装插件
-
导入jar包
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.24</version> <scope>provided</scope> </dependency>
-
在实体类上使用注解 pojo
@Getter and @Setter //get set 方法 @FieldNameConstants @ToString // toString 方法 @EqualsAndHashCode @AllArgsConstructor, @RequiredArgsConstructor and @NoArgsConstructor //常用 无参和有参数 @Log, @Log4j, @Log4j2, @Slf4j, @XSlf4j, @CommonsLog, @JBossLog, @Flogger, @CustomLog @Data // get set tostring 无参构造 @Builder @SuperBuilder @Singular @Delegate @Value @Accessors @Wither @With @SneakyThrows @StandardException @val @var experimental @var @UtilityClass
-
常用
@Data // get set tostring 无参构造 hashcode equals @AllArgsConstructor // 有参构造 @NoArgsConstructor // 无参 @EqualsAndHashCode @ToString @Getter
10、 多对一
- 多个学生,对应一个老师
- 对于学生而言,关联 多个学生,关联一个老师【多对一】 对象:association
- 对于老师而言,集合,一个老师有很多学生【一对多】 集合: collection
CREATE TABLE `teacher` (
`id` int(10) NOT NULL,
`name` varchar(30) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `student` (
`id` int(10) NOT NULL,
`name` varchar(30) NOT 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 teacher(`id`,`name`) values (1,'秦老师');
insert into student(`id`,`name`,`tid`) values
(1,"111","1"),
(2,"22","1"),
(3,"333","1"),
(4,"444","1"),
(5,"555","1"),
(6,"666","1");
测试环境
- 导入lombok
- 新建实体类
- 建立Mapper接口
- 建立Mapper.xml文件
- 在核心配置文件内绑定我们的Mapper接口或者文件!
- 测试能否成功
按照查询嵌套处理
<!--
思路
1. 查询所有的学生信息
2. 根据查询出来的学的的tid 寻找对应的老师!
-->
<select id="getStudent" resultMap="Student-Teacher">
select *
from student;
</select>
<resultMap id="Student-Teacher" 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>
按照结果查询处理
<!--
按照结果嵌套处理
-->
<select id="getStudent2" resultMap="Student-Teacher2">
select s.id sid,s.name sname,t.name tname
from student s,teacher t
where s.tid = t.id
</select>
<resultMap id="Student-Teacher2" type="Student">
<result property="id" column="sid"/>
<result property="name" column="sname"/>
<association property="teacher" javaType="Teacher">
<result property="name" column="tname"/>
</association>
</resultMap>
回顾mysql多对一查询方式:
- 子查询
- 联表查询
11、 一对多处理
比如一个老师有多个学生!
对应老师来说就是一对多的关系
实体类
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Teacher {
private int id;
private String name;
// 一个老师有多个学生
private List<Student> students;
}
按照查询嵌套处理
<select id="getTeachers2" resultMap="Teacher_student2">
select * from teacher where id = #{id}
</select>
<resultMap id="Teacher_student2" type="Teacher">
<result property="id" column="id"/>
<result property="name" column="name"/>
<collection property="students" javaType="ArrayList" ofType="Student" select="getstudentByTeacher" column="id"/>
</resultMap>
<select id="getstudentByTeacher" resultType="Student">
select * from student where tid = #{tid}
</select>
按照结果查询处理
<!--按结果嵌套查询-->
<select id="getTeachers" resultMap="Teacher_student">
select s.id sid, s.name sname, t.name tname,t.id tid
from teacher t,
student s
where t.id = s.tid and t.id = #{id}
</select>
<resultMap id="Teacher_student" type="Teacher">
<result property="id" column="tid"/>
<result property="name" column="tname"/>
<!--
集合 collection 对象:association
javaType 指定属性的类型
集合中的泛型信息 使用 ofType获取
-->
<collection property="students" ofType="student">
<result property="name" column="sname"/>
<result property="id" column="sid"/>
</collection>
</resultMap>
小问题 Property 一定要对应pojo类的容器不存在错误
Cause: org.apache.ibatis.reflection.ReflectionException: There is no setter for property named 'student' in 'class lmq.com.pojo.Teacher'
小结
- 关联 - association 【多对一】
- 集合 - collection 【一对多】
- javaType & ofType
- javaType用来指定实体类中属性的类型
- ofType 用来指定映射带List或者集合中pojo类型,泛型中的约束类型
注意点:
- 保持sql的可读性,尽量通俗易懂
- 注意一对多和多对一中,属性名和字段的问题
- 如果问题不好排错,可以使用日志,建议使用log4j
慢 sql 1s 1000s 别人1s 自己1000s 会被开除
面试高频
- mysql引擎
- innoDB底层原理
- 索引
- 索引优化
12、动态sql
什么是动态sql:动态sql就是指根据不同条件生成不同的sql语句
利用动态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
创建一个基础工程
-
导包
-
编写配置文件
-
编写实体类
package lmq.com.pojo; import lombok.Data; import java.util.Date; /** * @author 羡鱼 * @version 1.0 * @date 2023/6/22 2:13 */ @Data public class Blog { private String id; private String title; private String author; private Date createTime; //属性名和字段不一致 驼峰 和 _ <setting name="mapUnderscoreToCamelCase" value="true"/> private int views; }
-
编写实体类对应的Mapper接口 和 Mapper.xml文件
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>
choose (when, otherwise)
<select id="queryBloogChoose" 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
<select id="queryBlogIF" parameterType="map" resultType="blog">
select * from mybatis.BLOG
<where> <!-- 比较智能 会去掉 and or -->
<if test="title != null">
title = #{title}
</if>
<if test="author != null">
and author = #{author}
</if>
</where>
</select>
set
<update id="updateBlog" parameterType="map">
update mybatis.BLOG
<set>
<if test="title != null">
title = #{title},
</if>
<if test="author != null">
author = #{author},
</if>
<if test="views != null">
views = #{views}
</if>
</set>
where id = #{id}
</update>
trem 定制化
<trim prefix="WHERE" prefixOverrides="AND |OR ">
...
</trim>
<trim prefix="SET" suffixOverrides=",">
...
</trim>
所谓的动态sql 本质还是sql语句 只是我们可以在sql层面,去执行一个逻辑代码
Foreach
动态 SQL 的另一个常见使用场景是对集合进行遍历(尤其是在构建 IN 条件语句的时候)
<select id="selectPostIn" resultType="domain.blog.Post">
SELECT *
FROM POST P
<where>
<foreach item="item" index="index" collection="list"
open="ID in (" separator="," close=")" nullable="true">
#{item}
</foreach>
</where>
</select>
select * from user where 1=1 and (id = 1 or id =2 or id =3)
ids = [1,2,3]
select * from User
<where>
<foreach item="item" index="index" collection="ids" open="id IN (" separator="," close=")" nullable="true">
#{item}
</foreach>
</where>
动态sql就是在拼接sql语句,我们只要保证sql的正确,按照sql的格式,去排列组合就可以了
建议:
- 现在mysql中写出完整的sql,在去对应的修改为我们的动态sql
sql片段
有的时候我们可能会将一些公共部分抽取出来
-
使用sql标签抽取公共部分
<sql id="if-title-author-views"> <if test="title != null"> title = #{title}, </if> <if test="author != null"> author = #{author}, </if> <if test="views != null"> views = #{views} </if> </sql>
-
在要使用的地方使用include标签引用
<update id="updateBlog" parameterType="map"> update mybatis.BLOG <set> <include refid="if-title-author-views"/> </set> where id = #{id} </update>
注意实现:
- 最好基于单表来定义sql 片段
- 不要存在where标签
13、缓存
13.1、简介
读写分离,主从复制
查询 : 链接数据库 , 消耗资源!
一次查询的结果可以暂存在一个可以直接取到的地方 --> 内存 : 缓存
我们就不用再开启数据库连接 直接走缓存,就不用数据库了
-
什么是缓存【cache】?
- 存在内存中的临时数据
- 经用户经常查询的数据放在缓存(内存)中,用户去查询数据就不用再磁盘上(关系型数据库数据文件)查询 ,从缓存中查询,从而提高查询效率,解决高并发问题。
-
为什么使用缓存?
- 减少和数据的交互次数,减少系统开销,提高系统效率。
-
什么样的数据能使用缓存?
- 经常查询并且不经常改变的数据
13.2、Mybatis缓存
- mybatis 包含了一个非常强大的查询缓存特性,它可以非常方便地定制和配置缓存。缓存可以极大的提升查询效率。
- Mybatis 系统中默认定义了两级缓存:一级缓存和二级缓存
- 默认情况下,只开启一级缓存。(sqlsession级别,也称为本地缓存)
- 二级缓存需要手动开启和配置,它基于namespace级别的缓存。
- 为了提高拓展性,Mybatis定义了缓存接口Cache。我们可以通过实现Cache接口来自定义二级缓存
13.3、一级缓存
- 一级缓存也叫本地缓存:sqlsession
- 与数据库同一次会话期间查询到的数据会放在本地缓存中。
- 以后如果需要获取相同数据,直接从缓存中拿,没有必要再次查询数据库。
测试步骤:
-
开启日志!
-
测试在一个session中查询两次相同记录
-
查看日志输出
-
缓存失效
- 查询不同的东西
- 正删改操作可能会改变原来的数据,所以必定刷新
- 查询不同的Mapper.xml
- 手动清理缓存
sqlSession.clearCache();
小结: 一级缓存默认开启的,只在一次sqlsession中有效也就是拿到连接到关闭连接这个区间段!一级缓存就是一个Map。
13.4、二级缓存
-
二级缓存也叫全局缓存,一级缓存作用域太底了,所以诞生了二级缓存
-
基于namespace级别的缓存,一个名称空间,对应一个耳机缓存
-
工作机制
-
一个会话查询一条数据,这个数据就会被保存到当前会话的一级缓存中;
-
如果当前会话关闭,这个一级缓存就没有了,但是我们要的是会话关闭了,一级缓存中的数据被保存到二级缓存中;
-
新的会话信息就可以在二级缓存中获取;
-
不同的mapper查出的数据会放在自己对应的缓存(map)中;
-
步骤
- 开启全局缓存
<!-- 使用全局缓存 -->
<setting name="cacheEnabled" value="true"/>
- 在要使用二级缓存的Mapper中开启
<cache/>
也可以自定义参数
也可以自定义参数
<cache
eviction="FIFO"
flushInterval="60000"
size="512"
readOnly="true"/>
<!--
这个更高级的配置创建了一个 FIFO 缓存,每隔 60 秒刷新,最多可以存储结果对象或列表的 512 个引用,而且返回的对象被认为是只读的,因此对它们进行修改可能会在不同线程中的调用者产生冲突。
-->
-
测试 sqlsession关闭就存入二级缓存
-
问题 : 我们需要将实体类序列化!否则就会
caused by: java.io.NotSerializableException: lmq.com.pojo.User
// 序列化就可以了 package lmq.com.pojo; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import java.io.Serializable; /** * @author 羡鱼 * @version 1.0 * @date 2023/6/22 22:36 */ @Data @AllArgsConstructor @NoArgsConstructor public class User implements Serializable { private int id; private String name; private String pwd; }
-
小结
- 只要开启了二级缓存,在同一个mapper下就有效
- 所有的数据都会先放在一级缓存
- 只有当会话提交,或者关闭的时候,才会提交到二级缓存中!
13.5、缓存原理
13.6、 自定义缓存
Ehcache 是一种广泛使用的开源的java分布式缓存。主要面相通用缓存
mybatis-ehcache – MyBatis Ehcache |参考文档
要在程序中使用ehcache,要先导包
<!-- 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中指定使用我们的ehcache缓存实现!
<cache type="org.mybatis.caches.ehcache.EhBlockingCache"/>
配置文件
<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="https://www.ehcache.org/ehcache.xsd"
updateCheck="false">
<!--
diskStore: 为缓存路径,ehcache分为内存磁盘两级,此属性定义磁盘缓存位置
user.home -用户主目录
user.dir - 用户当前工作目录
java.io.tmpdir - 默认临时文件路径
-->
<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"/>
<!--
defaultCache:默认缓存策略,当ehcache 找不到定义缓存时,则使用这个缓存策略。只能定义一个。
-->
<!--
name: 缓存名字。
maxElementsInMemory: 缓存最大数目
maxElementsOnDisk: 硬盘最大缓存个数 若是0表示无穷大
eternal: 对象是否永久有效,一但设置timeout 将不起作用 此时会忽略timeToIdleSeconds和timeToLiveSeconds属性,默认为false
overflowToDisk: 是否保存到硬盘,当系统宕机时
timeToIdleSeconds: 设置对象在失效前的允许闲置时间(单位:秒)。仅当eternal=false对象不是永久有效时使用, 可选属性默认值是0
cache元素的属性:
name:缓存名称
maxElementsInMemory:内存中最大缓存对象数
maxElementsOnDisk:硬盘中最大缓存对象数,若是0表示无穷大
eternal:true表示对象永不过期,此时会忽略timeToIdleSeconds和timeToLiveSeconds属性,默认为false
overflowToDisk:true表示当内存缓存的对象数目达到了maxElementsInMemory界限后,会把溢出的对象写到硬盘缓存中。注意:如果缓存的对象要写入到硬盘中的话,则该对象必须实现了Serializable接口才行。
diskSpoolBufferSizeMB:磁盘缓存区大小,默认为30MB。每个Cache都应该有自己的一个缓存区。
diskPersistent:是否缓存虚拟机重启期数据
diskExpiryThreadIntervalSeconds:磁盘失效线程运行时间间隔,默认为120秒
timeToIdleSeconds: 设定允许对象处于空闲状态的最长时间,以秒为单位。当对象自从最近一次被访问后,如果处于空闲状态的时间超过了timeToIdleSeconds属性值,这个对象就会过期,EHCache将把它从缓存中清空。只有当eternal属性为false,该属性才有效。如果该属性值为0,则表示对象可以无限期地处于空闲状态
timeToLiveSeconds:设定对象允许存在于缓存中的最长时间,以秒为单位。当对象自从被存放到缓存中后,如果处于缓存中的时间超过了 timeToLiveSeconds属性值,这个对象就会过期,EHCache将把它从缓存中清除。只有当eternal属性为false,该属性才有效。如果该属性值为0,则表示对象可以无限期地存在于缓存中。timeToLiveSeconds必须大于timeToIdleSeconds属性,才有意义
memoryStoreEvictionPolicy:当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存。可选策略有:LRU(最近最少使用,默认策略)、FIFO(先进先出)、LFU(最少访问次数)。
-->
</ehcache>
Redis数据库来做缓存
没有什么是加一层解决不了的,如果有那就加两层
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· Vue3状态管理终极指南:Pinia保姆级教程