【个人随笔仅供参考,如有出错欢迎批评】
Mybatis
一.什么是Mybatis
持久层框架
什么是数据持久化?即数据在瞬时状态(内存断电即失)和持久状态(数据库、io文件)之间转换
mybatis特点:
1.简单易学:导包+配置sql映射文件(Mapper.xml)即可
2.灵活解耦:sql写在XxMapper.xml里统一管理,sql与java代码解耦
3.支持动态sql
什么是动态sql?动态sql就是根据不同的条件生成不同的sql语句,包括元素:if、choose(when,otherwise)、trim(where,set)、SQL片段、foreach
二.Mybatis快速入门
思路:搭建环境->导包>编码->测试
1.搭建环境
创建maven项目,创建简单数据库表;
CREATE DATABASE mybatis;
USE mybatis;
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user`(
id VARCHAR(20) NOT NULL PRIMARY KEY,
name VARCHAR(20),
pwd VARCHAR(20)
)ENGINE=INNODB DEFAULT CHARSET=utf8
INSERT INTO `user`(id,name,pwd) VALUES (1,'张三','123456')
2.导包
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.29</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.10</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
</dependencies>
<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.编码
- (1)目录结构
- (2)编写mybatis核心配置文件mybatis-config.xml(配置数据源)
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核心配置文件-->
<configuration>
<settings>
<!--开启驼峰命名自动映射:即从经典数据库列名 A_COLUMN 映射到经典 Java 属性名 aColumn。-->
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
<!--environments(环境配置):Mybatis可以配置多种环境,通过default指定一个。-->
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
<!--
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
-->
</dataSource>
</environment>
</environments>
<!--告诉MyBatis到哪里去找映射文件-->
<mappers>
<mapper resource="com/cc/mapper/UserMapper.xml"/>
</mappers>
<!-- <mappers>
<mapper class="com.cc.mapper.UserMapper"/>
</mappers>-->
</configuration>
- (3)编写db.properties(也可以写死在mybatis-config.xml中)
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
username=root
password=root
- (4)编写MybatisUtils工具类
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;
public class MybatisUtils {
private static SqlSessionFactory sqlSessionFactory;
static {
try {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
} catch (IOException e) {
e.printStackTrace();
}
}
//获取SqlSession连接
public static SqlSession getSession(){
return sqlSessionFactory.openSession(true);//增删改每次都需要提交事务才生效,这里设置自动提交
}
}
- (5)编写 UserMapper接口
public interface UserMapper {
List<User>getUsers();
int addUser(User user);
int deleteUserById(String id);
int updateUserById(User user);
}
-
(6)编写UserMapper.xml映射文件(相当于UserMapper接口实现类UserMapperImpl,由于Mybatis通过XxMapper统一管理sql,所以用xml文件编写啦),记得要在mybatis-config.xml里注册下该映射文件
这里先回顾下jdbc编程
public class UserMapperImpl implements UserMapper{
@Override
public List<User> getUsers() {
Connection connection=null;
PreparedStatement preparedStatement=null;
ResultSet resultSet=null;
List<User> users=new ArrayList<User>();
try {
//加载数据库驱动块
Class.forName("com.mysql.jdbc.Driver");
//通过驱动管理类获取数据库连接
connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai", "root", "root");
//定义sql语句
String sql="SELECT * FROM `user`";
//获取预处理preparedStatement
preparedStatement=connection.prepareStatement(sql);
//设置sql参数,index从1开始,例String sql="SELECT * FROM `user` WHERE name=?";
//preparedStatement.setString(1,"张三");
resultSet= preparedStatement.executeQuery();
while(resultSet.next()){
User user = new User();
user.setId(resultSet.getString("id"));
user.setName(resultSet.getString("name"));
user.setPwd(resultSet.getString("pwd"));
users.add(user);
}
} catch (ClassNotFoundException | SQLException e) {
e.printStackTrace();
}
return users;
}
}
问题分析
1.数据库连接创建、释放频繁造成系统资源浪费(连接池可解决)
2.sql语句、结果集解析存在硬编码,不易维护
引入Mybatis使用UserMapper.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">
<!--namespace=绑定一个对应的Dao/Mapper接口-->
<mapper namespace="com.cc.mapper.UserMapper">
<!--select查询语句-->
<select id="getUsers" resultType="com.cc.entity.User">
SELECT * FROM `user`
</select>
<!--insert-->
<insert id="addUser" parameterType="com.cc.entity.User">
insert into user (id,name,pwd) values (#{id},#{name},#{pwd})
</insert>
<!--delete-->
<delete id="deleteUserById" parameterType="String">
delete from user where id = #{id}
</delete>
<!--update-->
<update id="updateUserById" parameterType="com.cc.entity.User">
update user set name=#{name},pwd=#{pwd} where id = #{id}
</update>
</mapper>
- (7)编写测试类
@Test
public void testUserMapper(){
SqlSession sqlSession = MybatisUtils.getSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
//查
/* List<User> users = mapper.getUsers();
for (User user :
users) {
System.out.println(user);
}*/
User user = new User();
user.setId("3");
user.setName("王五");
user.setPwd("123456");
//增
mapper.addUser(user);
//删
mapper.deleteUserById("3");
user.setName("王五五");
//改
mapper.updateUserById(user);
sqlSession.commit(); //提交事务,重点!不写的话不会提交到数据库
sqlSession.close();
}
- (8)解决属性名和数据库名不一致的问题
方案一:在sql语句中为列名指定别名
方案二:使用结果集映射->ResultMap
-
(9)接口使用万能Map传参给mapper映射文件(参数过多时可以考虑该方案)
-
在接口方法中,形参直接是Map<String,Object>map
User selectUserByNP(Map<String,Object> map);
-
Map的key为sql语句的参数名即可
@Test public void testUserMapper(){ SqlSession sqlSession = MybatisUtils.getSession(); UserMapper mapper = sqlSession.getMapper(UserMapper.class); Map<String, Object> map = new HashMap<String, Object>(); map.put("name","张三"); map.put("pwd","123456"); User user = mapper.selectUserByNP(map); }
-
编写sql语句时,参数类型为map
<select id="selectUserByNP" parameterType="map" resultType="com.cc.entity.User"> select * from user where name = #{name} and pwd = #{pwd} </select>
-
三.日志
思考:我们在测试SQL的时候,要是能够在控制台输出 SQL 的话,是不是就能够有更快的排错效率?
Mybatis 通过使用内置的日志工厂提供日志功能。内置日志工厂将会把日志工作委托给下面的实现之一:SLF4J、Apache Commons Logging、Log4j2、Log4j、JDK logging
下述通过log4j实现sql日志信息打印:
- (1)导包
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.16</version>
</dependency>
- (2)通过在 MyBatis 配置文件 mybatis-config.xml 里面添加一项 setting 来选择其它日志实现。
<configuration>
<settings>
<setting name="logImpl" value="LOG4J"/>
</settings>
</configuration>
- (3)编写配置文件
#将等级为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/sql.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
四.分页
为什么需要分页?
1.对于用户来说:一页展示所有数据眼花缭乱
2.对于mysql服务器来说:一次查询所有数据,数据量大则非常耗时。所以,后台查询部分数据而不是全部数据。
步骤
- (1)Limit实现分页sql语句
SELECT * FROM table LIMIT stratIndex,pageSize
- (2)编写UserMapper接口
List<User> getUsersByPage(Map<String,Object> map)
- (3)编写UserMapper.xml映射文件
<!--分页-->
<select id="getUsersByPage" parameterType="map" resultType="com.cc.entity.User">
SELECT * FROM `user` LIMIT #{stratIndex},#{pageSize}
</select>
- (4)测试类
@Test
public void testGetUserByPage(){
Map<String, Object> map = new HashMap<>();
int currentPage=1;
int pageSize=2;
map.put("stratIndex",(currentPage-1)*pageSize);
map.put("pageSize",2);
SqlSession sqlSession = MybatisUtils.getSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> userList = mapper.getUsersByPage(map);
for (User user:
userList) {
System.out.println(user);
}
}
*起始位置=(当前页数-1)页面大小
五.多表查询
步骤
环境搭建
CREATE TABLE clazz(
id VARCHAR(10) PRIMARY KEY,
name VARCHAR(30) DEFAULT NULL
)ENGINE=INNODB DEFAULT CHARSET=utf8;
CREATE TABLE student(
id VARCHAR(10) PRIMARY KEY,
name VARCHAR(30) DEFAULT NULL,
cid VARCHAR(10) DEFAULT NULL,
FOREIGN KEY (cid)REFERENCES clazz(id)
)ENGINE=INNODB DEFAULT CHARSET=utf8;
INSERT INTO clazz VALUES('1','软件1班'),('2','软件2班');
INSERT INTO student VALUES('1','张三','1'),('2','李四','1'),('3','王五','2');
1.多对一
需求:获取所有学生信息及其对应的班级信息
@Data
public class Clazz {
private String id;
private String name;
}
@Data
public class Student {
private String id;
private String name;
private Clazz clazz;
}
public interface StudentMapper {
List<Student>getStudents();
List<Student>getStudents2();
}
(1)方法一:按查询嵌套处理
<?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">
<!--namespace=绑定一个对应的Dao/Mapper接口-->
<mapper namespace="com.cc.mapper.StudentMapper">
<!--
需求:获取所有学生信息及对应班级的信息
思路:
1. 获取所有学生的信息
2. 根据获取的学生信息的cid->获取班级的信息
3. 思考问题,这样学生的结果集中应该包含班级,该如何处理呢,数据库中我们一般使用关联查询
(1). 做一个结果集映射:StudentClazz
(2). StudentClazz结果集的类型为 Student
(3). association – 一个复杂类型的关联;使用它来处理关联查询
-->
<resultMap id="StudentClazz" type="com.cc.entity.Student">
<!--association关联属性,property属性名,javaType属性类型,column在多方的外键列名,内嵌select-->
<association property="clazz" column="cid" javaType="com.cc.entity.Clazz" select="getClazzByid"/>
</resultMap>
<select id="getStudents" resultMap="StudentClazz">
select * from student
</select>
<select id="getClazzByid" parameterType="String" resultType="com.cc.entity.Clazz">
select * from clazz where id=#{id}
</select>
</mapper>
(2)方法二:按结果嵌套处理
<?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">
<!--namespace=绑定一个对应的Dao/Mapper接口-->
<mapper namespace="com.cc.mapper.StudentMapper">
<!-- 需求:获取所有学生信息及对应班级的信息-->
<!--=========================按查询结果嵌套处理===========================-->
<!--思路:直接查询出结果,进行结果集的映射-->
<select id="getStudents2" resultMap="StudentClazz2">
select student.id sid,student.name sname,cid,clazz.name cname from student,clazz where student.cid=clazz.id
</select>
<resultMap id="StudentClazz2" type="com.cc.entity.Student">
<result property="id" column="sid"/>
<result property="name" column="sname"/>
<association property="clazz" javaType="com.cc.entity.Clazz">
<result property="id" column="cid"/>
<result property="name" column="cname"/>
</association>
</resultMap>
</mapper>
2.一对多
需求:获取指定班级信息及其学生信息
@Data
public class Student2 {
private String id;
private String name;
private String cid;
}
@Data
public class Clazz2 {
private String id;
private String name;
private List<Student2> student2s;
}
public interface ClazzMapper {
Clazz2 getClazz2ById(String id);
}
(1)方法一:按查询嵌套处理
<?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">
<!--namespace=绑定一个对应的Dao/Mapper接口-->
<mapper namespace="com.cc.mapper.ClazzMapper">
<!--按查询嵌套处理-->
<select id="getClazz2ById" parameterType="String" resultMap="ClazzStudent">
select * from clazz where id=#{id}
</select>
<select id="getStudentByCid" parameterType="String" resultType="com.cc.entity.Student2">
select * from student where student.cid=#{cid}
</select>
<resultMap id="ClazzStudent" type="com.cc.entity.Clazz2">
<!--column是一方主键列名-->
<collection property="student2s" javaType="ArrayList" ofType="com.cc.entity.Student2"
column="id" select="getStudentByCid"/>
</resultMap>
</mapper>
(2)方法二:按结果嵌套处理
<?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">
<!--namespace=绑定一个对应的Dao/Mapper接口-->
<mapper namespace="com.cc.mapper.ClazzMapper">
<!--方法2.按结果嵌套处理-->
<select id="getClazz2ById2" resultMap="ClazzStudent2" parameterType="String">
select cid,clazz.name cname,student.id sid,student.name sname
from clazz,student where clazz.id=student.cid and clazz.id=#{id}
</select>
<resultMap id="ClazzStudent2" type="com.cc.entity.Clazz2">
<result property="id" column="cid"/>
<result property="name" column="cname"/>
<collection property="student2s" ofType="com.cc.entity.Student2">
<result property="id" column="sid"/>
<result property="name" column="sname"/>
<result property="cid" column="cid"/>
</collection>
</resultMap>
</mapper>
3.总结
-
关联-association
-
集合-collection
- 所以association是用于一对一和多对一,而collection是用于一对多的关系
-
JavaType和ofType都是用来指定对象类型的
-
JavaType是用来指定pojo中属性的类型
-
ofType指定的是映射到list集合属性中pojo的类型。
-
六.动态SQL
什么是动态SQL?动态SQL就是根据不同的条件生成不同的SQL语句,本质还是拼接SQL语句
关于动态SQL,MyBatis需了解如下元素:
-
if
-
choose(when,otherwise)
-
trim(where,set)
-
foreach
七.注解开发
利用注解开发就不需要Xxmapper.xml映射文件啦(简单用注解,负责用映射文件)。 我们在XxMapper接口添加注解@Select()、@Insert()、@Delete()、@Update();此外还有@Param()
步骤:
1.在mybatis-config.xml核心配置文件编写:
<!--告诉MyBatis到哪里去找映射文件-->
<mappers>
<mapper resource="com/cc/mapper/UserMapper.xml"/>
</mappers>
<!-- <mappers>
<mapper class="com.cc.mapper.UserMapper"/>
</mappers>-->
2.在UserMapper编写接口
public interface UserMapper {
//注解开发
@Select("SELECT * FROM `user`")
List<User>getUsersByAnnotate();
@Insert("INSERT INTO `user`(id,name,pwd) VALUES(#{id},#{name},#{pwd})")
int addUserByAnnotate(User user);
@Insert("DELETE FROM `user` WHERE id=#{id}")
int deleteUserByIdByAnnotate(@Param("id") String id);
}
3.测试
@Test
public void testAnnotate(){
SqlSession sqlSession = MybatisUtils.getSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
//查
/* List<User> userList = mapper.getUsersByAnnotate();
for (User user:
userList) {
System.out.println(user);
}*/
//增
User user = new User("4","何散","123456");
/* mapper.addUserByAnnotate(user);*/
//删
mapper.deleteUserByIdByAnnotate("4");
}
关于@Param
@Param注解用于给方法的参数命名,为sql语句中的参数赋值服务
- 在方法参数一个(非JavaBean)的情况下,可以不使用@Param
- 在方法参数多个的情况下,建议使用@Param给参数命名
- 在方法参数JavaBean的情况下,则不能使用@Param;在SQL语句里会引用JavaBean的属性,而且只能引用JavaBean的属性。(所以,要确保实体类和数据库字段对应)
在Mybatis中,#{}和${}区别
#{} 是预编译语句(PrepareStatement)中的占位符?,可以防止SQL注入攻击,可自动转义
@Select("SELECT * FROM `user` WHERE id=#{id}")
User getUsersByIdByAnnotate(@Param("id")String id);
${}是普通的字符串拼接符,不能防止SQL注入攻击,若参数值包含特殊字符可能导致SQL语句失败
@Select("SELECT * FROM `user` WHERE id='${id}'")
User getUsersByIdByAnnotate(@Param("id")String id);
八.缓存
为减少与数据库交互,我们可以把经常查询且不易发生改变的数据放入缓存中
MyBatis系统中默认定义了两级缓存:一级缓存和二级缓存
1.一级缓存(默认开启)
SqlSession级别的缓存,也称本地缓存,缓存的数据只在SqlSession内有效。
@Test
public void testCache(){
//一级缓存
SqlSession sqlSession = MybatisUtils.getSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user1 = mapper.getUserById("1");
System.out.println(user1);
System.out.println("===============================");
User user2 = mapper.getUserById("1");
System.out.println(user2);
System.out.println("===============================");
System.out.println("user1==user2?"+(user1==user2));
sqlSession.close();
}
一级缓存失效的情况:
-
查询不同的元组(行)
-
增删改操作后会刷新缓存
-
查询不同的XxMapper.xml
-
手动清理缓存
session.clearCache();//手动清除缓存
2.二级缓存(手动开启)
mapper级别的缓存,也称全局缓存,同一个namespace公用这一个缓存。即多个sqlSession去操作同一个XxMapper.xml的sql语句,可以共享缓存。
-
工作机制
-
- 一个会话查询一条数据,这个数据就会被放在当前会话的一级缓存中;
- 如果当前会话关闭了,这个会话对应的一级缓存就没了;但是我们想要的是,会话关闭了,一级缓存中的数据被保存到二级缓存中;
- 新的会话查询信息,就可以从二级缓存中获取内容;
- 不同的xMapper.xml查出的数据会放在自己对应的缓存中;
-
使用步骤
- 开启全局缓存 【mybatis-config.xml】,默认开启
<setting name="cacheEnabled" value="true"/>
- 在要使用二级缓存的XxMapper.xml中开启
<cache/>
- 测试
SqlSession sqlSession1 = MybatisUtils.getSession(); UserMapper mapper1 = sqlSession1.getMapper(UserMapper.class); User user1 = mapper1.getUserById("1"); System.out.println(user1); sqlSession1.close(); System.out.println("==============================="); SqlSession sqlSession2 = MybatisUtils.getSession(); UserMapper mapper2 = sqlSession2.getMapper(UserMapper.class); User user2 = mapper2.getUserById("1"); System.out.println(user2); sqlSession2.close(); System.out.println("==============================="); System.out.println("user1==user2?"+(user1==user2));