这里写目录标题
- 搭建一个最简单的MyBatis程序
- 搭建一个最简单的MyBatis程序
- 错误问题
- 顺序
- 当数据库的列和实体的属性不能完全对应的时候,就要使用结果映射。
- 注意:只有select才能查询结果,才需要结果映射,所以resultMap是用在select中的。
- resultType和resultMap属性有什么区别呢?
- 主键自增⚠️只有insert才会涉及到主键自增
- 如何处理枚举和日期时间类型
- 建立表格,实体,Mapper,测试
- 当一个类出现在另一个类中的时候,我们就说这两个类产生了关联。一旦有了关联关系,就需要采用
- 关联映射
- 日志:记录程序运行过程中所产生的一些信息,这些信息用于帮助我们查看程序运行的状态和错误信息。
- 缓存:主要目的是为了加快查询的效率。也就是说只有查询才会用到缓存。
- 1. 一级缓存:MyBatis中的一级缓存实际上就是把查询出来的内容缓存在sqlSession中,这个就是一级缓存。一级缓存必须使用,不能关闭。
- 2. 二级缓存:二级缓存是跨越sqlSession的缓存,即二级缓存的内容并非存在于sqlSession中。
- 缓存
1. 什么是框架?
简历、楼房
我们自己书写代码经常有不规范的地方,但使用了别人的框架之后,约束和方便了我们代码的编写。
框架可以被认为是别人帮我们写好了绝大多数的规则和代码,你只需要按照这个框架的要求填写进去就可以了。
框架既然是别人写好的代码,那么他们一般是以一些jar包的方式提供给我们使用的。
2. 什么是SSM或SSH框架或三大框架整合?
SSH:Spring框架,Struts2框架,Hibernate/JPA框架
SSM:Spring框架,Spring MVC框架,MyBatis框架
当然,这些框架的组合并不是完全固定的,可以任意组合。
3. 什么是MyBatis框架?
MyBatis是一个持久层框架。专门负责与数据库的交互,即替代了以前的JDBC。
但是MyBatis并非是一个新的数据库连接技术,他的所有代码都是采用JDBC的基本技术书写的。
即Java中连接数据库只有JDBC,MyBatis只是在JDBC的基础上进行了加工,更方便我们使用了。
4. 什么是持久化?
将程序中的数据在持久状态(数据库)和瞬时状态(内存)之间进行转换的机制。
比如一个new User(1, “Tom”, 10);这个对象就是瞬时,断电即丢失或程序结束即丢失。
但是一旦他被存入数据库表格中,就被认为是持久的。
注意:持久化是一个双向的过程,即从数据库里读也算持久化
5. ORM(对象关系映射):Object Relational Mapping
关系:一个数据库表格就叫一个二元关系,简称关系,或者称作关系型表格
5.1 将一个对象和表格中的一行进行对应
id name age
1 tom 10 new User(1, “tom”, 10)
2 jerry 4 new User(2, “jerry”, 4)
3 ben 12 new User(3, “ben”, 12)
5.2 将一个类和一个表格的结构进行对应
class User{
Integer id;
String name;
Integer age;
}
create table users(
id int,
name varchar(30),
age int
);
搭建一个最简单的MyBatis程序
1. 删除和建立数据库表格,注意暂时不要使用复杂类型,如日期和枚举
drop table if exists users;
create table users(
id int,
name varchar(30),
age int
);
2. 建立实体
3. 建立dao层接口,我们就做2个功能,即插入和查询
public interface UserDao {
List select();
int insert(User user);
}
搭建一个最简单的MyBatis程序
1. 删除和建立数据库表格,注意暂时不要使用复杂类型,如日期和枚举
drop table if exists users;
create table users(
id int,
name varchar(30),
age int
);
2. 建立实体
3. 建立mapper层接口,
public interface UserMapper {
List select();
int insert(User user);
}
⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️
MyBatis习惯将Dao写成Mapper,并放入mapper包的里面
⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️
之后的做法有两种:
3.1 采用注解
在Mapper接口的每一个方法上,写好其对应的SQL语句
public interface UserMapper {
@Select(“select * from users”)
List select();
@Insert("insert into users(id, name, age) values(#{id}, #{name}, #{age})")
//JDBC中通常采用?进行占位,但是MyBatis需要使用#{}的形式来完成占位
//{}的里面需要写的是参数中属性的名字,即下面方法参数是User,{}中放置的是User中属性的名字
int insert(User user);
}
3.2 采用配置文件
4. 在resources文件夹中建立mybatis-config.xml文件(文件名是任意指定的,不一定非要叫这个名字)
<?xml version="1.0" encoding="UTF-8" ?> ⚠️⚠️⚠️数据库驱动 ⚠️⚠️⚠️数据库URL,有时区问题的同学记得加时区timeZone ⚠️⚠️⚠️ ⚠️⚠️⚠️ ⚠️⚠️⚠️此处是dao层接口的包名和接口名 ⚠️⚠️⚠️如果一个项目中有多个dao层的接口,需要在此处分别列出⚠️⚠️⚠️ ⚠️⚠️⚠️之所以这样做就是告诉MyBatis发现这个接口的存在⚠️⚠️⚠️5. 准备进行测试
⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️
只有接口怎么测试?MyBatis根据这个接口在后台帮我自动生成了实现类(看不到)
package com.google.db.dao;
import com.google.db.entity.User;
import com.google.db.mapper.UserMapper;
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 org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import static org.junit.Assert.*;
public class UserDaoTest {
private SqlSessionFactory sqlSessionFactory;
private SqlSession sqlSession;
@After//After表示之后的,即在所有Test方法执行之后,会执行这个方法的内容
public void after() throws IOException {
sqlSession.close(); //数据库连接用完后记得关闭
}
@Before //Before表示之前的,即在所有Test方法执行之前,会执行这个方法的内容
public void before() throws IOException {
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
//MyBatis提供了一个类Resources和方法getResourceAsStream用来在一个mybatis-config.xml文件上打开输入流
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//用这个文件中的信息构建一个重量级的对象sqlSessionFactory
//这个对象整个项目中应该只有一个,且不应该被轻易关闭,它代表了整个MySQL数据库的信息
sqlSession = sqlSessionFactory.openSession();
//sqlSession表示一个MyBatis到数据库的连接,类似于JDBC中的Connection
}
@Test
public void selectTest() throws IOException {
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
//getMapper方法的含义就是拿到MyBatis在后台帮我们生成的那个接口的实现
List<User> users = userMapper.select();
for(User user : users)
System.out.println(user); //users.forEach(System.out::println);
}
@Test
public void saveTest() throws IOException {
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
userMapper.insert(new User(2, "Jerry", 3));
sqlSession.commit(); //对于增删改,都需要提交事务
}
}
错误问题
-
junit测试方法不能有参数,不能有返回值
-
Caused by: org.xml.sax.SAXParseException; lineNumber: 2; columnNumber: 6;
-
xml文件第一行不要空白
-
Caused by: java.lang.ClassNotFoundException: Cannot find class: com.google.db.mapper.UserMapp1er
@Delete(“delete from users where id = #{id}”)
int delete(Integer id);@Update(“update users set name=#{name}, age=#{age} where id = #{id}”)
int update(User user);@Test
public void deleteTest() throws IOException {
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
userMapper.delete(2);
sqlSession.commit(); //对于增删改,都需要提交事务
}@Test
public void updateTest() throws IOException {
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
userMapper.update(new User(2, “Ben”, 5));
sqlSession.commit(); //对于增删改,都需要提交事务
}
顺序
-
建立数据库表格
-
建立实体
-
建立接口
public interface UserMapper {
List select();
int insert(User user);
int delete(Integer id);
int update(User user);
} -
为每一个接口,在mappers文件夹下建立对应的xml文件。
文件名是接口名.xml
这个文件,有些类似是这个接口的实现,其作用和之前的注解的作用是一致的。
⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️
mybatis-config.xml叫做mybatis的“配置文件”,每个项目一般只有一个,后期学习了Spring框架后,这个文件一般就没用了。
Mapper结尾的xml文件被称为“映射文件”,每个接口都有一个对应的映射文件
⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️
<!--如果一个方法有参数,就需要加好parameterType; 如果一个方法是查询,就需要加resultType-->
<select id="select" resultType="com.google.db.entity.User">
select * from users
</select>
<!--parameterType指一个方法中的参数类型,这里的类型也要写好包名和接口名-->
<insert id="insert" parameterType="com.google.db.entity.User">
insert into users(id, name, age) values(#{id}, #{name}, #{age})
</insert>
<update id="update" parameterType="com.google.db.entity.User">
update users set name=#{name}, age=#{age} where id = #{id}
</update>
<delete id="delete" parameterType="java.lang.Integer">
delete from users where id = #{id}
</delete>
-
修改mybatis-config.xml
将昨天的
修改为
-
org.apache.ibatis.binding.BindingException: Type interface com.google.db.mapper.UserMapper is not known to the MapperRegistry.
忘记在mybatis-config.xml中加入接口,无论是注解的还是xml形式的,忘记写了
看映射文件中,namespace后面的包和接口名是不是写错了。 -
Caused by: java.io.IOException: Could not find resource mapper/UserMapper.xml
文件不存在,仔细看路径,位置是否正确 -
Caused by: java.lang.ClassNotFoundException: Cannot find class: com.google.db.entity1.User
找不到类,即包名+类名写错了,
当数据库的列和实体的属性不能完全对应的时候,就要使用结果映射。
<resultMap id="userResultMap" type="com.google.db.entity.User" >
<!--id是一个resultMap的名字,任意指定-->
<!--type是表明哪个实体和数据库中的表格进行对应,值是这个实体的全路径-->
<id column="id" property="id" javaType="java.lang.Integer" jdbcType="INTEGER" />
<!--id表示主键,column表示数据库中列的名字,property表示实体中属性的名字-->
<!--javaType是property属性在java中的类型, jdbcType是column在数据库中的类型-->
<result column="name" property="name" javaType="java.lang.String" jdbcType="VARCHAR" />
<!--result表示一个普通列,其余和id一样-->
<result column="age" property="age" javaType="java.lang.Integer" jdbcType="INTEGER" />
</resultMap>
仅仅在xml文件中加入结果映射是没用的,还需要对其进行使用。
在哪里使用呢?
注意:只有select才能查询结果,才需要结果映射,所以resultMap是用在select中的。
s elect * from users这里的resultMap="userResultMap"就表示这个查询语句的结果,采用一个ID是userResultMap的结果映射来完成。
resultType和resultMap属性有什么区别呢?
resultType=“com.google.db.entity.User”
resultMap=“userResultMap”
尽量在程序中使用resultMap,除非你这个实体和表格的列名能够被MyBatis自动对应
主键自增⚠️只有insert才会涉及到主键自增
drop table if exists users;
create table users
(
id int primary key auto_increment,
name varchar(30),
age int
);
<insert id="insert" parameterType="com.google.db.entity.User" useGeneratedKeys="true" keyProperty="id">
insert into users(name, age) values(#{name}, #{age})
</insert>
@Test
public void saveTest() throws IOException {
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
userMapper.insert(new User("Jerry", 3));
sqlSession.commit(); //对于增删改,都需要提交事务
}
如何处理枚举和日期时间类型
drop table if exists logs;
create table logs(
id int primary key auto_increment,
login_time timestamp,
done enum(‘T’, ‘F’)
);
建立实体
public enum Done {
T,F
}
public class Log {
private Integer id;
private LocalDateTime loginTime;
private Done done;
......
}
-
建立和实体对应的接口
public interface LogMapper {
int insert(Log log);
List select();
} -
建立LogMapper.xml
<select id="select" resultMap="logResultMap">select * from logs</select>
<insert id="insert">
</insert>
关于作业的插入:
insert into users2 (id, name, funkyNumber, roundingMode) values (
#{id}, #{name}, #{funkyNumber}, #{roundingMode, typeHandler=org.apache.ibatis.type.EnumTypeHandler}
)
-
在mybatis的配置文件中加入
-
测试
@Test
public void selectTest() {
LogMapper logMapper = sqlSession.getMapper(LogMapper.class);
logMapper.select().forEach(System.out::println);
}
insert into logs(done, login_time) values(#{done, typeHandler=org.apache.ibatis.type.EnumTypeHandler}, #{loginTime})
@Test
public void saveTest() {
LogMapper logMapper = sqlSession.getMapper(LogMapper.class);
logMapper.insert(new Log(LocalDateTime.of(2020,1,1,1,1,1), Done.F));
sqlSession.commit();
}
建立表格,实体,Mapper,测试
要求表格有int, varchar, 日期/时间/时间戳,枚举,主键自增
drop table if exists users;
create table users
(
u_id int primary key auto_increment,
email varchar(50),
password varchar(30),
login_count int,
last_login_time timestamp
);
drop table if exists logs;
create table logs
(
l_id int primary key auto_increment,
u_id int,
login_time timestamp
);
- 如果两个表格在数据库中存在主外键关系,应该采用关联映射
当一个实体中存在对应的外键时,我们更愿意将外键直接写为另一个实体的引用,而不是参照数据库中的列类型了。
就我们当前的例子来说,Log这个实体中存在一个外键,即uid
public class Log {
private Integer lId;
private Integer uId; //这一列代表的就是外键,目前来讲,他是和数据库中的列类型保持一致的。
private LocalDateTime loginTime;
}
但是我现在更愿意改写为下面的形式
public class Log {
private Integer lId;
private User user; //将外键直接写为另一个实体的引用
private LocalDateTime loginTime;
}
- 对接口进行适当改写
⚠️⚠️⚠️⚠️⚠️MyBatis的接口不支持方法重载⚠️⚠️⚠️⚠️⚠️
public interface UserMapper {
int insert(User user);
String selectByEmail(String email);
User selectByEmailAndPassword(@Param(“email”) String email, @Param(“password”)String password);
int update(User user);
}
当MyBatis的方法有多个参数时,每个参数应该加上@Param注解
User selectByEmailAndPassword(@Param(“email”) String email, @Param(“password”)String password);
@Param(“这里的内容就是稍后在SQL语句中#{}中的名字”)
当一个类出现在另一个类中的时候,我们就说这两个类产生了关联。一旦有了关联关系,就需要采用
insert into logs(u_id, login_time) values(#{user.uId}, #{loginTime})注意:#{user.uId}
<resultMap id="logResultMap" type="com.google.entity.Log">
<id column="l_id" property="lId" />
<result column="login_time" property="loginTime" jdbcType="TIMESTAMP" javaType="java.time.LocalDateTime" />
<association column="u_id" property="user" jdbcType="INTEGER" javaType="com.google.entity.User">
<id column="u_id" property="uId"/>
<result column="email" property="email"/>
<result column="password" property="password"/>
<result column="login_count" property="loginCount"/>
<result column="last_login_time" property="lastLoginTime" javaType="java.time.LocalDateTime" jdbcType="TIMESTAMP"/>
</association>
</resultMap>
为了让关联映射能够起作用,一般都是要做两表连接的,因为只有SQL语句能查询出结果,Java中才有可能将这些结果存入对应的实体。
当结果从数据库中查询出来之后,需要哪些内容被填充,就要看是否进行映射了,比如password
关联映射
关联映射分为单向关联和双向关联,怎么区分?
一个类中包含另一个类的引用,就是单向。互相包含就是双向,比如
class A{}
class B{
private A a; //
}
此时我们说A和B是单向关联
class C{
private List ds = new ArrayList<>();
}
class D{
private C c;
}
此时我们说C和D是双向关联
class C{
private D d;
}
class D{
private C c;
}
<insert id="insert" parameterType="com.google.entity.Log">
insert into logs(u_id, login_time) values(#{user.uId}, #{loginTime})
</insert>
<select id="select" parameterType="java.lang.Integer" resultMap="logResultMap">
select * from logs l inner join users u on l.u_id = u.u_id
where l.u_id = #{uId}
</select>
@Test
public void saveTest() {
LogMapper logMapper = sqlSession.getMapper(LogMapper.class);
User user = new User();
user.setuId(1);
Log log = new Log(1, user, LocalDateTime.now());
logMapper.insert(log);
sqlSession.commit();
}
@Test
public void selectTest() {
LogMapper logMapper = sqlSession.getMapper(LogMapper.class);
logMapper.select(1).forEach(System.out::println);
}
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
日志:记录程序运行过程中所产生的一些信息,这些信息用于帮助我们查看程序运行的状态和错误信息。
日志文件的名字是log4j.properties
log4j.rootLogger=ERROR, stdout
log4j.logger.com.google.mapper.LogMapper=TRACE
log4j.logger.com.google.mapper.UserMapper=TRACE
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n
缓存:主要目的是为了加快查询的效率。也就是说只有查询才会用到缓存。
1. 一级缓存:MyBatis中的一级缓存实际上就是把查询出来的内容缓存在sqlSession中,这个就是一级缓存。一级缓存必须使用,不能关闭。
DEBUG [main] - > Preparing: select * from users where email=? and password=?
DEBUG [main] - > Parameters: jerry@google.com(String), 654321(String)
TRACE [main] - < Columns: u_id, email, password, login_count, last_login_time
TRACE [main] - < Row: 1, jerry@google.com, 654321, 0, 2020-07-29 21:59:52
DEBUG [main] - <== Total: 1
User{uId=1, email=‘jerry@google.com’, password=‘654321’, loginCount=0, lastLoginTime=2020-07-29T21:59:52}
DEBUG [main] - ==> Preparing: select * from users where email=? and password=?
DEBUG [main] - > Parameters: xxx(String), yyy(String)
DEBUG [main] - < Total: 0
null
DEBUG [main] - > Preparing: select * from users where email=? and password=?
DEBUG [main] - > Parameters: jerry@google.com(String), 654321(String)
TRACE [main] - < Columns: u_id, email, password, login_count, last_login_time
TRACE [main] - < Row: 1, jerry@google.com, 654321, 0, 2020-07-29 21:59:52
DEBUG [main] - <== Total: 1
User{uId=1, email=‘jerry@google.com’, password=‘654321’, loginCount=0, lastLoginTime=2020-07-29T21:59:52}
User{uId=1, email=‘jerry@google.com’, password=‘654321’, loginCount=0, lastLoginTime=2020-07-29T21:59:52}
@Test
public void selectByEmailAndPasswordTest() {
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
System.out.println(userMapper.selectByEmailAndPassword("jerry@google.com", "654321"));
sqlSession.close();
sqlSession = sqlSessionFactory.openSession();
userMapper = sqlSession.getMapper(UserMapper.class);
System.out.println(userMapper.selectByEmailAndPassword("jerry@google.com", "654321"));
}
sqlSession.close();缓存一旦被关闭,之前一级缓存的结果就丢失了。
2. 二级缓存:二级缓存是跨越sqlSession的缓存,即二级缓存的内容并非存在于sqlSession中。
即便sqlSession被关闭,二级缓存的内容依然存在。二级缓存可以选择的,默认是关闭的。
缓存
-
一级缓存:sqlSession级别的缓存,不能关闭,必须使用。
-
二级缓存:跨sqlSession的缓存,即sqlSession关闭后,内容依然存在于二级缓存中。
二级缓存默认是关闭的,想要使用需要先开启。
加入映射文件,之后就启用了二级缓存
想要把查询出来的内容放入二级缓存,要求这个内容必须是可序列化的。
MyBatis的二级缓存默认是如何实现的?
mybatis自带的二级缓存其实不太好用,所以通常我们会用一些第三方工具如redis等来替代它自身的二级缓存。
当一个SQL语句会根据不同条件发生变化时,我们就考虑使用动态SQL
select * from users where id = #{id} 不完整
比如我的一个SQL要求是uId不空就按照uId查询,如果uId为空就按照email查询
select * from users where u_id = #{uId}
select * from users where email = #{email}
List select(User user);
select * from users and email = #{email} and login_count >= #{loginCount} and email = #{email} and login_count >= #{loginCount}@Test
public void selectTest() throws InterruptedException {
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
User user = new User();
//user.setEmail("ben@google.com");
//user.setLoginCount(3);
userMapper.select(user).forEach(System.out::println);
}
select * from users and email = #{email}
<when test="loginCount != null">
and login_count >= #{loginCount}
</when>
<when test="email != null and loginCount != null">
and email = #{email} and login_count >= #{loginCount}
</when>
</choose>
</where>
</select>
update users email=#{email}, password=#{password}, login_count=#{loginCount}, last_login_time=#{lastLoginTime} where u_id=#{uId}
select *
from users
where u_id in (1, 3, 5, 7);
List selectByIds(@Param(“ids”) List ids);
<select id="selectByIds" resultMap="userResultMap">
select * from users where u_id in
<foreach item="uId" collection="ids" open="(" separator="," close=")">
#{uId}
</foreach>
</select>
@Test
public void selectTest() throws InterruptedException {
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
List<Integer> ids = new ArrayList<>();
ids.add(1); ids.add(3); ids.add(5); ids.add(7);
userMapper.selectByIds(ids).forEach(System.out::println);
}
<?xml version="1.0" encoding="UTF-8"?>
4.0.0
<groupId>com.google</groupId>
<artifactId>spring</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.8.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-test -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.2.8.RELEASE</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.6.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>