MyBatis
1.简介
1.1.什么是MyBatis
- MyBatis 是一款持久层框架
- 几乎避免了所有的JDBC代码和手动设置参数以及获取结果集
- 支持定制化sql,存储过程,高级映射
- MyBatis可以使用简单的xml或注解来配置映射原生类型,接口,java的POJO(Plain Old Java Objects)为数据库中的记录
获取myBatis方式
-
maven仓库
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.4.6</version> </dependency>
1.2.什么是持久化(动作)
数据持久化------>将数据存入到数据库中
持久状态和瞬时状态相互转化的过程
1.3.什么是持久层(概念)
持久层DAO层(Mapper):
DAO层主要是做数据持久层的工作,负责与数据库进行联络的一些任务都封装在此,
- DAO层的设计首先是设计DAO的接口,
- 然后在Spring的配置文件中定义此接口的实现类,
- 然后就可在模块中调用此接口来进行数据业务的处理,而不用关心此接口的具体实现类是哪个类,显得结构非常清晰,
- DAO层的数据源配置,以及有关数据库连接的参数都在Spring的配置文件中进行配置
业务层(Service层):
- Service层:Service层主要负责业务模块的逻辑应用设计。
- 首先设计接口,再设计其实现的类
- 接着再在Spring的配置文件中配置其实现的关联。这样我们就可以在应用中调用Service接口来进行业务处理。
- Service层的业务实现,具体要调用到已定义的DAO层的接口.
- 封装Service层的业务逻辑有利于通用的业务逻辑的独立性和重复利用性,程序显得非常简洁。
表现层(Controller层)
-
Controller层:Controller层负责具体的业务模块流程的控制,
-
在此层里面要调用Service层的接口来控制业务流程,
-
控制的配置也同样是在Spring的配置文件里面进行,针对具体的业务流程,会有不同的控制器,我们具体的设计过程中可以将流程进行抽象归纳,设计出可以重复利用的子单元流程模块,这样不仅使程序结构变得清晰,也大大减少了代码量。
view层
- 与用户交互的可视化层jsp的页面展示
1.4.为什么需要MyBatis
-
简单易学
-
灵活,sql与代码分离
-
消除sql与程序代码之间的耦合
-
提供映射标签,对象关系映射标签,xml标签,支持编写动态sql
-
使用广泛
2.第一个MyBatis程序
思路:搭建环境----->导入MyBatis----->编写代码------->测试
2.1.搭建环境
搭建数据库
mysql>create database mybatis;
mysql>use mybatis;
mysql> create table if not exists user(
-> id int(10) not null primary key,
-> name varchar(20) default null,
-> pwd varchar(20) default null
-> )engine=innoDB default charset=utf8;
Database changed
mysql> show tables;
+-------------------+
| Tables_in_mybatis |
+-------------------+
| user |
+-------------------+
1 row in set
mysql> insert into user(id,name,pwd) values
-> (1,'张三','123'),
-> (2,'李四','456'),
-> (3,'王五','789');
Database changed
Records: 3 Duplicates: 0 Warnings: 0
mysql> select * from user;
+----+------+-----+
| id | name | pwd |
+----+------+-----+
| 1 | 张三 | 123 |
| 2 | 李四 | 456 |
| 3 | 王五 | 789 |
+----+------+-----+
3 rows in set
新建一个项目
1.新建一个普通的maven项目
2.删除src目录(这是一个父工程,后面可以有多个子项目)
3.导入maven依赖
<!--导入依赖-->
<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.2</version>
</dependency>
<!--junit-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
</dependencies>
2.2.创建一个模块
-
编写MyBatis核心配置文件
<?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?useUnicode=true&characterEncoding=UTF-8 & useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC"/> <property name="username" value="root"/> <property name="password" value="123456"/> </dataSource> </environment> </environments> <!--此处少了mapper配置文件,见后面易错点--> </configuration>
-
使用idea连接mysql(5.2版本)报错:
java.lang.RuntimeException: com.mysql.cj.exceptions.InvalidConnectionAttributeException: The server time zone value 'Öйú±ê׼ʱ¼ä' is unrecognized or represents more than one time zone. You must configure either the server or JDBC driver (via the serverTimezone configuration property) to use a more specifc time zone value if you want to utilize time zone support.
解决办法:JDBC驱动程序的5.2版本与UTC时区配合使用,必须在连接字符串中明确指定serverTimezone。
jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=UTF-8&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC
-
-
编写MyBatis的工具类
//sqlSessionFactory--->sqlSession public class MyBatisUtils { private static SqlSessionFactory sqlSessionFactory; static { try { //使用mybatis第一步:获取sqlSessionFactory对象 String sources = "mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(sources); sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); } catch (IOException e) { e.printStackTrace(); } } //既然有了 SqlSessionFactory,顾名思义,我们可以从中获得 SqlSession的实例。 //可以通过 SqlSession 实例来直接执行已映射的 SQL 语句 public static SqlSession getSqlSession(){ return sqlSessionFactory.openSession(); }
2.3编写代码
-
实体类
public class user { private int id; private String name; private String pwd; public user() { } public user(int id, String name, String pwd) { this.id = id; this.name = name; this.pwd = pwd; } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getPwd() { return pwd; } public void setPwd(String pwd) { this.pwd = pwd; } @Override public String toString() { return "user{" + "id=" + id + ", name='" + name + '\'' + ", pwd='" + pwd + '\'' + '}'; } }
-
dao接口
public interface userDao { //获取全部列表 List<user> getUserList(); }
-
dao接口实现类(由原来的UserDaoImpl--------->userMapper配置文件)
<?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,即一个mapper绑定一个接口-->
<mapper namespace="com.hong.dao.userDao">
<!--查询语句-->
<!--id里面写接口的方法 resultType要写完全限定名-->
<select id="getUserList" resultType="com.hong.pojo.user">
select * from mybatis.user
</select>
</mapper>
2.4.单元测试
junit测试
public class userDaoTest {
@Test
public void test() {
//获取sqlSession对象
SqlSession sqlSession = MyBatisUtils.getSqlSession();
//方式一:getMapper
userDao userDao = sqlSession.getMapper(userDao.class);
List<user> userList = userDao.getUserList();
//方式二
// List<user> userList1=sqlSession.selectList("com.hong.dao.userDao.getUserList");
//for遍历
for (user u : userList) {
System.out.println(u);
}
//关闭sqlSession
sqlSession.close();
}
}
注意易错点:
1.配置文件没有注册
org.apache.ibatis.binding.BindingException: Type interface com.hong.dao.userDao is not known to the MapperRegistry.
MapperRegistry是什么?为什么没有注册?
dao层中每一个mapper都需要在mybatis-config.xml核心配置文件中配置注册
<!--每一个mapper.xml都需要在mybatis核心配置文件中注册-->
<!--所有路径必须以斜杠隔离-->
<mappers>
<mapper resource="com/hong/dao/userMapper.xml"/>
</mappers>
2.maven导出资源问题
java.lang.ExceptionInInitializerError 初始化异常错误
资源过滤问题:maven由于他的约定大于配置,我们之后可以能遇到我们写的配置文件无法被导出或者生效的问题,解决方案为:
<!--在build中配置resource,来防止我们资源导出失败的问题--> <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>
3.绑定接口错误
4.方法名不对
5.返回类型不对
2.5测试成功
2.6三剑客
SqlSessionFactoryBuilder
这个类可以被实例化、使用和丢弃,一旦创建了 SqlSessionFactory,就不再需要它了。 因此 SqlSessionFactoryBuilder 实例的最佳作用域是方法作用域(也就是局部方法变量)。 你可以重用 SqlSessionFactoryBuilder 来创建多个 SqlSessionFactory 实例,但最好还是不要一直保留着它,以保证所有的 XML 解析资源可以被释放给更重要的事情。
SqlSessionFactory
SqlSessionFactory 一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建另一个实例。 使用 SqlSessionFactory 的最佳实践是在应用运行期间不要重复创建多次,多次重建 SqlSessionFactory 被视为一种代码“坏习惯”。因此 SqlSessionFactory 的最佳作用域是应用作用域。 有很多方法可以做到,最简单的就是使用单例模式或者静态单例模式。
SqlSession
每个线程都应该有它自己的 SqlSession 实例。SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域。 绝对不能将 SqlSession 实例的引用放在一个类的静态域,甚至一个类的实例变量也不行。 也绝不能将 SqlSession 实例的引用放在任何类型的托管作用域中,比如 Servlet 框架中的 HttpSession。 如果你现在正在使用一种 Web 框架,考虑将 SqlSession 放在一个和 HTTP 请求相似的作用域中。 换句话说,每次收到 HTTP 请求,就可以打开一个 SqlSession,返回一个响应后,就关闭它。 这个关闭操作很重要,为了确保每次都能执行关闭操作,你应该把这个关闭操作放到 finally 块中。 下面的示例就是一个确保 SqlSession 关闭的标准模式:
3.CRUD
3.1.namespace
namespace中的包名要和Dao/mapper接口的包名一致
3.2.select
选择查询语句
- id 对应namespace中的方法名
- 返回值类型 sql语句执行的返回值
- 写接口userMapper.java
//根据id查询数据 user getUserById(int id);
- xml里写sql
<select id="getUserById" resultType="com.hong.pojo.user" parameterType="int"> select *from mybatis.user where id = #{id} </select>
- Junit测试(注意关闭sqlsession)
@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(); }
- 结果
3.3.add
- 写接口userMapper.java
//添加一个用户 int addUser(user user);
- xml里写sql
<!--对象中的属性可以直接取出来--> <insert id="addUser" parameterType="com.hong.pojo.user"> insert into mybatis.user(id, name, pwd) VALUES (#{id},#{name},#{pwd}) </insert>
- Junit测试(注意提交事务和关闭sqlsession)
@Test public void addUser(){ SqlSession sqlSession=MyBatisUtils.getSqlSession(); userMapper mapper = sqlSession.getMapper(userMapper.class); int num = mapper.addUser(new user(4, "小四", "444")); if(num>0){ System.out.println("添加成功"); } /*增删改必须要提交事务*/ sqlSession.commit(); sqlSession.close(); }
-
结果
3.4update
- 写接口userMapper.java
//根据id修改用户 int updateUser(user user);
- xml里写sql
<update id="updateUser" parameterType="com.hong.pojo.user"> update mybatis.user set id=#{id},name=#{name},pwd=#{pwd} where id=#{id}; </update>
- Junit测试(注意提交事务和关闭sqlsession)
@Test public void updateUser(){ SqlSession sqlSession = MyBatisUtils.getSqlSession(); userMapper mapper = sqlSession.getMapper(userMapper.class); int num = mapper.updateUser(new user(4, "小四", "444")); if(num>0){ System.out.println("修改成功"); } sqlSession.commit(); sqlSession.close(); }
- 结果
3.5.delete
-
写接口userMapper.java
//根据id删除用户 int deleteUser(int id);
-
xml里写sql
<delete id="deleteUser" parameterType="int"> delete from mybatis.user where id=#{id}</delete>
-
Junit测试(注意提交事务和关闭sqlsession)
@Testpublic void deleteUser(){ SqlSession sqlSession = MyBatisUtils.getSqlSession(); userMapper mapper = sqlSession.getMapper(userMapper.class); int num = mapper.deleteUser(4); if(num>0){ System.out.println("删除成功"); } sqlSession.commit(); sqlSession.close();}
3.6.常见错误
-
标签匹配问题
-
resource绑定mapper需要使用路径
-
程序配置文件需要规范,尤其是数据库的配置文件
-
NullPointException异常:myBatisUtils没有注册到资源------->获取sqlSessionFactory对象
//三行死代码 MyBatisUtils类中//使用mybatis第一步:获取sqlSessionFactory对象 String sources = "mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(sources); sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
-
xml文件输出存在乱码------->设置utf-8格式
-
maven资源导出问题------->静态资源过滤
3.7.万能Map
当我们的实体类,或者数据库中表的字段过多的时候,可以考虑使用Map
- 接口userMapper.java
//通过map添加一个用户int addUserByMap(Map map);
- xml里写sql
<!--通过map添加用户,values属性不用一一对应--> <insert id="addUserByMap" parameterType="map"> insert into mybatis.user(id,name,pwd) values (#{userId},#{userName},#{userPassword}) </insert>
- 测试
@Test public void addUserByMap(){ SqlSession sqlSession = MyBatisUtils.getSqlSession(); userMapper mapper = sqlSession.getMapper(userMapper.class); Map map=new HashMap(); map.put("userId",5); map.put("userName","小五"); map.put("userPassword","5555"); int num = mapper.addUserByMap(map); if(num>0){ System.out.println("插入成功"); } sqlSession.commit(); sqlSession.close(); }
- 结果
4.配置解析
1.核心配置
MyBatis 的配置文件包含了会深深影响 MyBatis 行为的设置和属性信息。 配置文档的顶层结构如下:
- configuration(配置)
- properties(属性)
- settings(设置)
- typeAliases(类型别名)
- typeHandlers(类型处理器)
- objectFactory(对象工厂)
- plugins(插件)
- environments(环境配置)
- environment(环境变量)
- transactionManager(事务管理器)
- dataSource(数据源)
- databaseIdProvider(数据库厂商标识)
- [mappers(映射器)]
2.环境配置
不过要记住:尽管可以配置多个环境,但每个 SqlSessionFactory 实例只能选择一种环境。
<environments default="development"> <environment id="development"> <transactionManager type="JDBC"> <property name="..." value="..."/> </transactionManager> <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.属性
<!--出现下列报错是是因为核心配置文件configuration中属性有顺序,要按规定的顺序来写一些属性The content of element type "configuration" must match"properties,.setings?.typeAiases,typeHandlers,objectfactory?.objectWapperFactorj?.refectorfactoryi.plugins?,environments,databaseldProvider,mapperst) .-->
出现下面错误是因为在db.properties文件中后面加了分号,此文件中每行末尾不要加分号
- db.properties
driver=com.mysql.jdbc.Driverurl=jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=utf-8&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTCusername=rootpassword=123456
4.别名
- 按配置文件顺序写出别名属性
<!--别名--> <typeAliases> <typeAlias type="com.hong.pojo.books" alias="books"></typeAlias> </typeAliases>
- 可以将全类名改写成别名
<select id="queryAllBooks" resultType="books"> select *from mybatis.books; </select>
- 测试
com.hong.test.UnitTest,queryAllBooksbooks{id=1, name='Spring实战', price=97.0, info='SSH框架'}books{id=2, name='C++面向对象', price=48.0, info='加油,GoGoGo'}books{id=3, name='Linux编程', price=79.0, info='从入门到入狱'}Process finished with exit code 0
5.设置
一个配置完整的 settings 元素的示例如下:
<settings> <setting name="cacheEnabled" value="true"/> <setting name="lazyLoadingEnabled" value="true"/> <setting name="multipleResultSetsEnabled" value="true"/> <setting name="useColumnLabel" value="true"/> <setting name="useGeneratedKeys" value="false"/> <setting name="autoMappingBehavior" value="PARTIAL"/> <setting name="autoMappingUnknownColumnBehavior" value="WARNING"/> <setting name="defaultExecutorType" value="SIMPLE"/> <setting name="defaultStatementTimeout" value="25"/> <setting name="defaultFetchSize" value="100"/> <setting name="safeRowBoundsEnabled" value="false"/> <setting name="mapUnderscoreToCamelCase" value="false"/> <setting name="localCacheScope" value="SESSION"/> <setting name="jdbcTypeForNull" value="OTHER"/> <setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/></settings>
6.映射器
映射器(mappers)
既然 MyBatis 的行为已经由上述元素配置完了,我们现在就要来定义 SQL 映射语句了。 但首先,我们需要告诉 MyBatis 到哪里去找到这些语句。 在自动查找资源方面,Java 并没有提供一个很好的解决方案,所以最好的办法是直接告诉 MyBatis 到哪里去找映射文件。 你可以使用相对于类路径的资源引用,或完全限定资源定位符(包括 file:///
形式的 URL),或类名和包名等。例如:
- 方式一:mapperRegistry配置:注册绑定mapper文件,最常用
<!-- 使用相对于类路径的资源引用 --><!--每一个mapper.xml都需要在mybatis核心配置文件中注册--><!--所有路径必须以斜杠隔离--><mappers> <mapper resource="com/hong/dao/bookMapper.xml"/></mappers>
- 方式二:使用class文件绑定注册
<!--每一个mapper.xml都需要在MyBatis核心配置文件中注册--><mappers><mapper class="com.hong.dao.bookMapper"/></mappers>
-
注意点:
- 接口和它的mapper配置文件必须同名!
- 接口和它的mapper配置文件必须在同一个包下
-
方式三:使用扫描包进行(与class绑定注册类似)
- 接口和它的mapper配置文件必须同名!
- 接口和它的mapper配置文件必须在同一个包下
<mappers><package name="com.kuang.dao" /></mappers>
7.生命周期与作用域
理解我们之前讨论过的不同作用域和生命周期类别是至关重要的,因为错误的使用会导致非常严重的并发问题。
提示 对象生命周期和依赖注入框架
依赖注入框架可以创建线程安全的、基于事务的 SqlSession 和映射器,并将它们直接注入到你的 bean 中,因此可以直接忽略它们的生命周期。 如果对如何通过依赖注入框架使用 MyBatis 感兴趣,可以研究一下 MyBatis-Spring 或 MyBatis-Guice 两个子项目。
SqlSessionFactoryBuilder
这个类可以被实例化、使用和丢弃,一旦创建了 SqlSessionFactory,就不再需要它了。 因此 SqlSessionFactoryBuilder 实例的最佳作用域是方法作用域(也就是局部方法变量)。 你可以重用 SqlSessionFactoryBuilder 来创建多个 SqlSessionFactory 实例,但最好还是不要一直保留着它,以保证所有的 XML 解析资源可以被释放给更重要的事情。
SqlSessionFactory
SqlSessionFactory 一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建另一个实例。 使用 SqlSessionFactory 的最佳实践是在应用运行期间不要重复创建多次,多次重建 SqlSessionFactory 被视为一种代码“坏习惯”。因此 SqlSessionFactory 的最佳作用域是应用作用域。 有很多方法可以做到,最简单的就是使用单例模式或者静态单例模式。
SqlSession
每个线程都应该有它自己的 SqlSession 实例。SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域。 绝对不能将 SqlSession 实例的引用放在一个类的静态域,甚至一个类的实例变量也不行。 也绝不能将 SqlSession 实例的引用放在任何类型的托管作用域中,比如 Servlet 框架中的 HttpSession。 如果你现在正在使用一种 Web 框架,考虑将 SqlSession 放在一个和 HTTP 请求相似的作用域中。 换句话说,每次收到 HTTP 请求,就可以打开一个 SqlSession,返回一个响应后,就关闭它。 这个关闭操作很重要,为了确保每次都能执行关闭操作,你应该把这个关闭操作放到 finally 块中。 下面的示例就是一个确保 SqlSession 关闭的标准模式:
try (SqlSession session = sqlSessionFactory.openSession()) { // 你的应用逻辑代码}
在所有代码中都遵循这种使用模式,可以保证所有数据库资源都能被正确地关闭。
5.resultMap结果集映射
-
constructor
- 用于在实例化类时,注入结果到构造方法中
-
idArg
- ID 参数;标记出作为 ID 的结果可以帮助提高整体性能 -
arg
- 将被注入到构造方法的一个普通结果
-
-
id
– 一个 ID 结果;标记出作为 ID 的结果可以帮助提高整体性能 -
result
– 注入到字段或 JavaBean 属性的普通结果 -
association
– 一个复杂类型的关联;许多结果将包装成这种类型
- 嵌套结果映射 – 关联可以是
resultMap
元素,或是对其它结果映射的引用
- 嵌套结果映射 – 关联可以是
-
collection
– 一个复杂类型的集合
- 嵌套结果映射 – 集合可以是
resultMap
元素,或是对其它结果映射的引用
- 嵌套结果映射 – 集合可以是
-
discriminator
– 使用结果值来决定使用哪个
resultMap
-
case
– 基于某些值的结果映射
- 嵌套结果映射 –
case
也是一个结果映射,因此具有相同的结构和元素;或者引用其它的结果映射
- 嵌套结果映射 –
-
5.日志
1.日志工厂
Mybatis 通过使用内置的日志工厂提供日志功能。内置日志工厂将会把日志工作委托给下面的实现之一:
- SLF4J
- Apache Commons Logging
- Log4j 2
- Log4j
- JDK logging
2. log4j
- 导入maven的jar包
<dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency>
- 配置Log4j.properties
#将等级为DEBUG的日志信息输出到console和file这两个目的地,console和file的定义在下面的代码log4j.rootLogger=DEBUG,console,file#控制台输出设置log4j.appender.console=org.apache.log4j.ConsoleAppenderlog4j.appender.console.Target=System.outlog4j.appender.console.Threshold=DEBUGlog4j.appender.console.layout=org.apache.log4j.PatternLayoutlog4j.appender.console.layout.ConversionPattern=[%c]-%m%n#文件输出设置log4j.appender.file=org.apache.log4j.RollingFileAppenderlog4j.appender.file.File=./log/hong.loglog4j.appender.file.MaxFileSize=10mblog4j.appender.file.Threshold=DEBUGlog4j.appender.file.layout=org.apache.log4j.PatternLayoutlog4j.appender.file.layout.ConversionPattern=[%p][%d{yy-MM-dd}][%c]%m%n#日志输出级别log4j.logger.org.mybatis=DEBUGlog4j.logger.java.sql=DEBUGlog4j.logger.java.sql.statement=DEBUGlog4j.logger.java.sql.ResultSet=DEBUGlog4j.logger.java.sql.PreparedStatement=DEBUG
- 日志实现
<!--日志--> <settings> <setting name="logImpl" value="LOG4J"/> </settings>
- 测试
6.分页
1.为什么要分页
目的:减少数据处理量
2.limit分页
select *from user limit startIndex,pageSize;select *from user limit 3; #[0,n]
3. 使用MyBatis实现分页
- 接口
//分页List<User> getUserByLimit(Map<String,Integer> map);
-
mapper.xml
<!--分页--><select id="getBookByLimit" parameterType="map" resultMap="bookMap"> select *from mybatis.books limit #{startIndex},#{pageSize}</select>
-
测试
@Testpublic void getuserByLimitOisq1session sq1session = Mybatisutils.getsqlsession(;bookMapper mapper = sqlsession. getMapper(bookMapper.class);HashMap<string,Integer> map = new HashMap<String,Integer>(;map.put(""startIndex",1);map.put("pagesize"",2);List<Book> bookList = mapper.getbookByLimit(map);for (books book : bookList) isystem.out.print1n(book);}sqlsession . c1ose();}
7.注解
1.注解开发
- 注解在接口上实现
//查询全部书籍 //注解不需要分号结尾 @Select("select *from mybatis.books") List<books> queryAllBooks();
- 需要在配置文件中绑定接口
<mappers> <mapper class="com.hong.dao.bookMapper"/></mappers>
- 测试
@Test public void test(){ SqlSession sqlSession = MyBatisUtils.getSqlSession(); bookMapper mapper = sqlSession.getMapper(bookMapper.class); List<books> booksList = mapper.queryAllBooks(); for (books b:booksList){ System.out.println(b); } }
2.注解开发CRUD
不需要xml文件配置,建议在不复杂且不多sql的情况下使用
- 写注解sql
//booksMapper.javapackage com.hong.dao;import com.hong.pojo.books;import org.apache.ibatis.annotations.*;import java.util.List;public interface bookMapper { //增加书籍 @Insert("insert into mybatis.books(id,name,price,info) values " + "(#{id},#{name}, #{price}, #{info})") int addBook(books book); //删除书籍 @Delete("delete from mybatis.books where id=#{id}") int deleteBook(@Param("id")int id); //根据id修改书籍 //特别注意此处sql语句 set中不要写id,在where中写id @Update(" update mybatis.books set name=#{name},price=#{price},info=#{info} where id=#{id}") int update(books book); //根据id查询书籍 @Select("select *from mybatis.books where id=#{id}") books queryBookById(@Param("id") int id); //查询全部书籍 //注解不需要分号结尾 @Select("select *from mybatis.books") List<books> queryAllBooks();}
- 测试
package com.hong.test;import com.hong.dao.bookMapper;import com.hong.pojo.books;import com.hong.utils.MyBatisUtils;import org.apache.ibatis.session.SqlSession;import org.junit.Test;import java.util.List;public class UnitTest { @Test public void addBook(){ SqlSession sqlSession = MyBatisUtils.getSqlSession(); bookMapper mapper = sqlSession.getMapper(bookMapper.class); books book=new books(4,"C语言",30,"从入门到精通"); int i = mapper.addBook(book); if(i>0){ System.out.println("添加成功"); } sqlSession.close(); } @Test public void deleteBook(){ SqlSession sqlSession = MyBatisUtils.getSqlSession(); bookMapper mapper = sqlSession.getMapper(bookMapper.class); int i = mapper.deleteBook(4); if(i>0){ System.out.println("删除成功"); } sqlSession.close(); } @Test public void updateBook(){ SqlSession sqlSession = MyBatisUtils.getSqlSession(); bookMapper mapper = sqlSession.getMapper(bookMapper.class); books book=new books(1,"C++语言",9,"从入门到精通"); int i=mapper.update(book); if(i==1){ System.out.println("修改成功!!!"); } sqlSession.close(); } @Test public void queryAllBooks(){ SqlSession sqlSession = MyBatisUtils.getSqlSession(); bookMapper mapper = sqlSession.getMapper(bookMapper.class); List<books> booksList = mapper.queryAllBooks(); for (books b:booksList){ System.out.println(b); } } @Test public void queryBookById(){ SqlSession sqlSession = MyBatisUtils.getSqlSession(); bookMapper mapper = sqlSession.getMapper(bookMapper.class); books books = mapper.queryBookById(1); System.out.println(books); }}
- @Param();注解
- 基本类型的参数和String类型需要加上
- 引用类型不需要加
- 只有一个基本类型不需要加,可以忽略,建议都加
- 在sql中引用就是设定的属性名
8.Lombok
- 下载插件
idea:file--->settings--->Plugins--->lombok
- 导入jar或者导入maven依赖
<groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.10</version>
- 增加注解
@Data@AllArgsConstructor@NoArgsConstructor
9.多对一处理
对于学生:学生与老师就是多对一
-
idea项目不能编译的小错误
Caused by: java.io.IOException: Could not find resource com.hong.dao.StudentMapper
- 注意:mybatis-config.xml绑定的mapper.xml文件格式不对,正确格式如下:
<mappers> <mapper resource="com/hong/dao/StudentMapper.xml"/> <mapper resource="com/hong/dao/TeacherMapper.xml"/></mappers>
方法:按照查询嵌套处理
查询全部学生及对应的老师名称
- mapper.xml文件
<?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="com.hong.dao.StudentMapper"> <select id="getAllStudent" resultMap="getStudentTeacher"> select * from mybatis.student; </select> <!--一个property属性对应数据库的一个列column--> <resultMap id="getStudentTeacher" type="Student"> <result property="id" column="id" /> <result property="name" column="name" /> <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.java
@Test public void getAllStudent(){ SqlSession sqlSession = MyBatisUtils.getSqlSession(); StudentMapper mapper = sqlSession.getMapper(StudentMapper.class); List<Student> allStudent = mapper.getAllStudent(); for (Student s:allStudent){ System.out.println(s); } }
- 测试结果
方法:按照结果嵌套处理
- mapper.xml(核心)
<!--按照查询嵌套--> <select id="getAllStudent2" resultMap="StudentTeacher"> select s.id sid,s.name sname,t.name tname from student s,teacher t where t.id=s.tid; </select> <resultMap id="StudentTeacher" type="Student"> <result property="id" column="sid"/> <result property="name" column="sname"/> <association property="teacher" javaType="Teacher"> <result property="name" column="tname" /> </association> </resultMap>
- Test
@Test public void getAllStudent2(){ SqlSession sqlSession = MyBatisUtils.getSqlSession(); StudentMapper mapper = sqlSession.getMapper(StudentMapper.class); List<Student> allStudent2 = mapper.getAllStudent2(); for (Student s:allStudent2){ System.out.println(s); }
- 结果
com.hong.test.MyTest,getAllStudent2Student{id=1, name='张三', teacher=Teacher{id=0, name='秦老师'}}Student{id=2, name='李四', teacher=Teacher{id=0, name='秦老师'}}Student{id=3, name='王五', teacher=Teacher{id=0, name='秦老师'}}Student{id=4, name='李六', teacher=Teacher{id=0, name='许老师'}}Student{id=5, name='赵七', teacher=Teacher{id=0, name='许老师'}}Student{id=6, name='于八', teacher=Teacher{id=0, name='许老师'}}Process finished with exit code 0
10.一对多处理
1.方式
对于老师来说:老师与学生就是一对多
两种方式:1.按结果嵌套处理
2.按查询嵌套处理
2.步骤
- 实体类
package com.hong.pojo;import lombok.AllArgsConstructor;import lombok.Data;import lombok.NoArgsConstructor;@Data@NoArgsConstructor@AllArgsConstructorpublic class Student { private int id; private String name; private int tid;}
package com.hong.pojo;import lombok.AllArgsConstructor;import lombok.Data;import lombok.NoArgsConstructor;import java.util.List;@Data@NoArgsConstructor@AllArgsConstructorpublic class Teacher { private int id; private String name; List<Student> students;}
- TeacherMapper接口
package com.hong.dao;import com.hong.pojo.Teacher;import org.apache.ibatis.annotations.Param;import java.util.List;public interface TeacherMapper { //获取老师 List<Teacher> getTeacher(); //获取指定老师及其学生信息 //按照结果嵌套处理 Teacher getTeacherStu(@Param("tid") int id); //按照查询嵌套处理 Teacher getTeacherStu2(@Param("tid") int id);}
- TeacherMapper.xml
<?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="com.hong.dao.TeacherMapper"> <select id="getTeacher" resultType="Teacher"> select * from mybatis.teacher; </select> <!--按结果嵌套处理--> <select id="getTeacherStu" resultMap="TeacherStudent"> select t.id tid,t.name tname,s.id sid,s.name sname from student s,teacher t where t.id=s.tid and tid=#{tid}; </select> <resultMap id="TeacherStudent" type="Teacher"> <result property="id" column="tid"/> <result property="name" column="name"/> <collection property="students" ofType="Student"> <result property="id" column="sid"/> <result property="name" column="sname"/> <result property="tid" column="tid"/> </collection> </resultMap> <!--========================================================--> <!--按查询嵌套处理--> <select id="getTeacherStu2" resultMap="TeacherStudent2"> select *from mybatis.teacher where id=#{tid} </select> <resultMap id="TeacherStudent2" type="Teacher"> <collection property="students" javaType="ArrayList" ofType="Student" select="getStudentByTid" column="id"/> </resultMap> <select id="getStudentByTid" resultType="Student"> select * from mybatis.student where tid=#{tid} </select></mapper>
- Test测试
package com.hong.test;import com.hong.dao.TeacherMapper;import com.hong.pojo.Student;import com.hong.pojo.Teacher;import com.hong.utils.MyBatisUtils;import org.apache.ibatis.session.SqlSession;import org.junit.Test;import java.util.List;public class MyTest { @Test public void getTeacher(){ SqlSession sqlSession = MyBatisUtils.sqlSession(); TeacherMapper mapper = sqlSession.getMapper(TeacherMapper.class); List<Teacher> teacher = mapper.getTeacher(); for (Teacher t:teacher){ System.out.println(t); } }//按结果嵌套 @Test public void getTeacherStudent(){ SqlSession sqlSession = MyBatisUtils.sqlSession(); TeacherMapper mapper = sqlSession.getMapper(TeacherMapper.class); Teacher teacherStu = mapper.getTeacherStu(1); System.out.println(teacherStu); sqlSession.close(); }//按查询嵌套 @Test public void getTeacherStu2(){ SqlSession sqlSession = MyBatisUtils.sqlSession(); TeacherMapper mapper = sqlSession.getMapper(TeacherMapper.class); Teacher teacherStu2 = mapper.getTeacherStu2(1); System.out.println(teacherStu2); /* 查询结果 * Teacher{id=0, name='秦老师', * students=[ *Student {id=1, name='张三', tid=1}, * Student{id=2, name='李四', tid=1}, * Student{id=3, name='王五', tid=1} * ]} * */ }}
3.小结
-
关联:association---->多对一
-
集合:collection------->一对多
-
JavaType:用来指定实体类中属性的类型
-
ofType:指定映射到List或者集合中的pojo类型,泛型中的约束类型
-
注意点:
-
- 保证sql的可读性
- 注意一对多和多对一的字段属性问题
- 可以使用日志
-
面试高频问题:
-
- mysql引擎
- InnoDB底层
- 索引
- 索引优化
11.动态SQL
1.什么是动态sql:根据条件生成不同的sql语句
- if
- choose (when, otherwise)
- trim (where, set)
- foreach
- 随机生成ID
package com.hong.test;import org.junit.Test;import java.util.UUID;public class MyTest { public String getId(){ return UUID.randomUUID().toString().replaceAll("-",""); } @Test public void test(){ System.out.println(getId()); System.out.println(getId()); System.out.println(getId()); }}/*测试结果7acb6d82de1343ffadb5cd7b4184d5efcd6d846927014e4aba83d2c32d637d8efeaaf186d8a94914875b213285818e2c*/
2.IF语句
- mapper接口
//查询博客之IF List<Blog> queryBlogIF(HashMap map);
- xml配置
<select id="queryBlogIF" parameterType="map" resultType="Blog"> select * from mybatis.blog where 1=1 <if test="title != null"> and title=#{title} </if> <if test="auther != null"> and auther=#{auther} </if> </select>
- Test
@Test public void queryBlogIF(){ SqlSession sqlSession = MyBatisUtils.sqlSession(); BlogMapper mapper = sqlSession.getMapper(BlogMapper.class); HashMap map = new HashMap(); map.put("auther","Walls"); List<Blog> blogs = mapper.queryBlogIF(map); for (Blog blog:blogs){ System.out.println(blog); } sqlSession.close();}
- 测试结果
Blog(id=6e6a13e741824ac8b243100177e00383, title=Redis教学, auther=Walls, createTime=Thu Feb 18 12:21:50 CST 2021, views=250)Blog(id=ac8f7babbfcd44cb960cb0ad939925b6, title=Spring实战, auther=Walls, createTime=Thu Feb 18 12:21:13 CST 2021, views=985)
3.choose
- mapper接口
//作者名确定时,根据title查找博客 List<Blog> findTitleBlog(HashMap map);
- xml文件
<!--作者名为Walls时,根据map查找指定title的信息,默认title为Redis教学"--><select id="findTitleBlog" resultType="Blog"> SELECT * FROM mybatis.blog WHERE auther="Walls" <choose> <when test="title!= null"> AND title like #{title} </when> <otherwise> AND title="Redis教学" </otherwise> </choose> </select>
- Test
@Test public void findTitleBlog() { SqlSession sqlSession = MyBatisUtils.sqlSession(); BlogMapper mapper = sqlSession.getMapper(BlogMapper.class); HashMap map = new HashMap(); map.put("title","Spring实战"); List<Blog> titleBlogLike = mapper.findTitleBlog(map); for (Blog blog : titleBlogLike) { System.out.println(blog); } }
- console 测试结果
Blog(id=ac8f7babbfcd44cb960cb0ad939925b6, title=Spring实战, auther=Walls, createTime=Thu Feb 18 12:21:13 CST 2021, views=985)
4.foreach
动态 SQL 的另一个常见使用场景是对集合进行遍历(尤其是在构建 IN 条件语句的时候)
foreach 元素的功能非常强大,它允许你指定一个集合,声明可以在元素体内使用的集合项(item)和索引(index)变量。它也允许你指定开头与结尾的字符串以及集合项迭代之间的分隔符。这个元素也不会错误地添加多余的分隔符
你可以将任何可迭代对象(如 List、Set 等)、Map 对象或者数组对象作为集合参数传递给 foreach。当使用可迭代对象或者数组时,index 是当前迭代的序号,item 的值是本次迭代获取到的元素。当使用 Map 对象(或者 Map.Entry 对象的集合)时,index 是键,item 是值。
- mapper接口
List<Blog> queryBlogForeach(Map map);
- mapper.xml
<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
@Test public void queryBlogForeach() { SqlSession sqlSession = MyBatisUtils.sqlSession(); BlogMapper mapper = sqlSession.getMapper(BlogMapper.class); Map map = new HashMap(); ArrayList ids = new ArrayList(); ids.add("d6079c15bdeb4cc68a371fcbde01def6"); ids.add("2df28ca0160d40ef811638889695983e"); map.put("ids", ids); List<Blog> blogs = mapper.queryBlogForeach(map); for (Blog blog : blogs) { System.out.println(blog); } sqlSession.close(); }
- console
Blog(id=2df28ca0160d40ef811638889695983e, title=Linux入门, auther=Alice, createTime=Thu Feb 18 12:19:27 CST 2021, views=66)Blog(id=d6079c15bdeb4cc68a371fcbde01def6, title=Java精通, auther=Alice, createTime=Thu Feb 18 12:18:07 CST 2021, views=15)
5.sql片段
<!--把sql提取出来,提高复用性--><sql id="queryBlogIF-sql"> select * from mybatis.blog where 1=1 <if test="title != null"> and title=#{title} </if> <if test="auther != null"> and auther=#{auther} </if> </sql> <select id="queryBlogIF" parameterType="map" resultType="Blog"> <include refid="queryBlogIF-sql" /> </select>
- 动态SQL就是在拼接sql,保证sql的正确性,再去排列组合即可
12.缓存(了解)
1.什么是缓存
-
放在内存中的临时数据
-
把用户经常查询的数据放到缓存中,查询就不需要调用数据库查询,提高了查询效率,解决了了高并发系统的性能问题
-
缓存能减少和数据库的交互次数,减少系统开销
-
经常查询且不经常改变的数据适合用缓存
2.Mybatis缓存
MyBatis 内置了一个强大的事务性查询缓存机制,它可以非常方便地配置和定制。 为了使它更加强大而且易于配置,我们对 MyBatis 3 中的缓存实现进行了许多改进。
默认情况下,只启用了本地的会话缓存,它仅仅对一个会话中的数据进行缓存。 要启用全局的二级缓存,只需要在你的 SQL 映射文件中添加一行:
<cache/>
https://mybatis.org/mybatis-3/zh/sqlmap-xml.html#cache
-
Mybatis包含一个很强大的查询缓存特性,可以方便的定制和配置缓存,可以极大的提升查询效率
-
Mybatis默认定义了两级缓存,一级缓存和二级缓存
- 默认下,只开启一级缓存,sqlSession级别缓存,也叫本地缓存
- 二级缓存需要手机开启和配置,基于namespace级别的缓存
- 为提高扩展性,MyBatis定义了缓存接口Cache,我们可以通过cache接口定义二级缓存
3. 一级缓存
- 一级缓存也叫本地缓存:
- 与数据库同一次会话期间查询到的数据会放到本地缓存
- 以后若需要获取相同数据,可直接从缓存中去拿,不需查询数据库
- 从getSession()缓存开始到sqlSession.close()缓存结束
- 缓存失效的情况:
- 增删改操作:会改变原来的数据,必定会刷新缓存
- 查询不同的数据
- 查询不同的mapper
- 手动清理缓存
4.二级缓存
二级缓存是事务性的。这意味着,当 SqlSession 完成并提交时,或是完成并回滚,但没有执行 flushCache=true 的 insert/delete/update 语句时,缓存会获得更新
-
二级缓存也叫全局缓存,一级缓存的作用域太低了,所以诞生了二级缓存
-
基于namespace级别的缓存,一个命名空间对应一个二级缓存
-
工作机制:
- 一个会话查询一条数据,这个数据会被放在当前会话的一级缓存中
- 当前会话若关闭了,这个会话对应的一级缓存就没了,但我们想要的是,一级缓存中的数据被保存到二级缓存中
- 新的会话查询信息,就可以从二级缓存中获取内容
- 不同的mapper查出的数据会放在自己的缓存map中
-
步骤
- 1.开启全局缓存
<!--显式地开启全局缓存--><setting name="cacheEnabled" value="true"/>
-
-
- 在要使用二级缓存得mapper中开启
-
<cache />
- 也可自定义参数
<cache eviction="FIFI" flushInterval="60000" size="512" readOnly="true" />
5.缓存原理
- 缓存顺序
-
- 先看二级缓存中有没有
- 再看一级缓存中有没有
- 查询数据库
6.ehcache自定义缓存
EhCache 是一个纯Java的进程内缓存框架,具有快速、精干等特点,是Hibernate中默认的CacheProvider。
基本介绍:
Ehcache是一种广泛使用的开源Java分布式缓存。主要面向通用缓存,Java EE和轻量级容器。它具有内存和磁盘存储,缓存加载器,缓存扩展,缓存异常处理程序,一个gzip缓存servlet过滤器,支持REST和SOAP api等特点。
Ehcache最初是由Greg Luck于2003年开始开发。2009年,该项目被Terracotta购买。软件仍然是开源,但一些新的主要功能(例如,快速可重启性之间的一致性的)只能在商业产品中使用,例如Enterprise EHCache and BigMemory。维基媒体Foundationannounced目前使用的就是Ehcache技术。
现在基本都是Redis做缓存