目录
参考文档:https://mybatis.org/mybatis-3/zh/
什么是Mybatis
非常简单地讲,
Mybatis是一种持久层框架,
Mybatis支持对象关系映射,即一个类对应一张表。
持久层: 具有将将数据持久化功能的层,即将瞬时的数据保存到数据库或是硬盘中,使其能长期保存
对象关系映射:即Mapper,通过一个Mapper接口对应一张表,通过xml文件指定该接口来实现数据库的操作
使用Mybatis简单步骤
1. 新建
a. 新建一个空Maven项目,删除src目录添加模块
b. 新建文件夹及文件,具体如图
2. 基础的配置和类的写法
mybatis-config.xml是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>
<!-- 环境,可以配置多套,在default中选择使用环境的id切换 -->
<environments default="development">
<environment id="development">
<transactionManager type="JDBC" />
<dataSource type="POOLED">
<!-- 加载驱动,value为驱动类名(根据实际填写) -->
<property name="driver" value="com.mysql.cj.jdbc.Driver" />
<!-- 数据库位置及连接参数配置,&是&的转义 -->
<property name="url" value="jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=UTF-8&userSSL=true&serverTimezone=GMT%2B8" />
<!-- 数据库用户名及密码 -->
<property name="username" value="root" />
<property name="password" value="112159" />
</dataSource>
</environment>
</environments>
<!-- 映射文件位置,写全包名 -->
<mappers>
<mapper resource="com/hoppi/mapper/UserMapper.xml" />
</mappers>
</configuration>
需按次序写标签
MybatisUtil是Mybatis的工具类,调用其中方法得到一个SqlSession对象,示例如下
package com.hoppi.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;
public class MybatisUtil {
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();
}
}
public static SqlSession getSqlSession() {
return sqlSessionFactory.openSession();
}
}
User是实体类,即和表对应的类,具有表中字段对应的属性和Getter/Setter方法以及toString方法(可有可无吧),示例如下
package com.hoppi.pojo;
public class User {
//属性
private int id;
private String name;
private String password;
//无参构造
public User() {
}
//有参构造
public User(int id, String name, String password) {
this.id = id;
this.name = name;
this.password = password;
}
//Getter/Setter
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 getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
//toString
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
", password='" + password + '\'' +
'}';
}
}
想偷懒的话可以导入Lombok包(或添加Maven依赖),为这个类加
@Data
、@AllArgsConstructor
(有参构造)、@NoArgsConstructor
(无参构造)注释,如果有父类再加个@EqualsAndHashCode(callSuper = true)
,这样只要写属性就行了
值得注意的是Lombok写pojo这种大量的简单的重复的类时用用就行了,这玩意缺陷很多,争议也很多,可以参考这篇文章:Lombok的优劣势
UserMapper是User类对应的映射接口(注意是接口),其中有数据库增删改查等方法声明,示例如下
package com.hoppi.mapper;
import com.hoppi.pojo.User;
import java.util.List;
public interface UserMapper {
//打印user表
List<User> getUserList();
User getUserById(int id);
int insert(User user);
}
UserMapper.xml则是UserMapper接口对应配置文件,其中包括接口映射和各种JDBC(如select)元素,接口映射的命名空间用于声明该文件对应的接口,配置如下
<?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.hoppi.mapper.UserMapper">
<!-- id为对应接口中的方法名 resultType(resultMap)为返回类型 -->
<select id="getUserList" resultType="com.hoppi.pojo.User">
select * from mybatis.users
</select>
<!-- parameterType为传入参数类型 #{}类似占位符,其中的内容为对应方法传入参数名 -->
<select id="getUserById" parameterType="int" resultType="com.hoppi.pojo.User">
select * from mybatis.users where id = #{id}
</select>
<!-- -->
<insert id="insert" parameterType="com.hoppi.pojo.User">
insert into mybatis.users(id,name,password) values (#{id},#{name},#{password})
</insert>
</mapper>
3. 快进到使用
第一步,获得SqlSession对象
//工具类静态方法getSqlSession得到SqlSession对象
SqlSession sqlSession = MybatisUtil.getSqlSession();
第二步,获得Mapper对象
//这里获得UserMapper对象
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
第三步,使用Mapper中的方法
注意:增删改需要提交事务,即sqlSession.commit();
如果不想手动提交,可以在Mybatis工具类中给openSession()方法传递一个true
第四步,关闭SqlSession
sqlSession.close();
稍微高级一点的写法
1. Map
当表的字段过多而我们不希望修改一条记录中所有字段,或插入时不希望插入有默认值或可为空的字段,以及多条件查询时,我们可以使用Map类型传参,当然,其他的操作想的话也可以这么用,但是一般还是用实体类
至于为什么用Map,因为Mybatis只能传递一个参数
示例如下
//来一个新的insert方法 int insert2(Map<String, Object> map);
<!-- xml中配置, user_id、user_name、user_password为传入Map中的key --> <insert id="insert2" parameterType="map"> insert into mybatis.users(id,name,password) values (#{user_id},#{user_name},#{user_password}) </insert>
//来个测试方法 @org.junit.Test public void test() { SqlSession sqlSession = MybatisUtil.getSqlSession(); UserMapper mapper = sqlSession.getMapper(UserMapper.class); for (User user : mapper.getUserList()) { System.out.println(user); } //关键操作 Map<String, Object> map = new HashMap<>(); map.put("user_id", "10000005"); map.put("user_name", "狗子"); map.put("user_password", "1919810"); mapper.insert2(map); sqlSession.commit(); for (User user : mapper.getUserList()) { System.out.println(user); } sqlSession.close(); }
2. 外部配置文件
可以在Mybatis配置文件中引入配置文件
第一步·新建配置文件,例如
第二步·写入参数
driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=UTF-8&userSSL=true&serverTimezone=GMT%2B8
username=root
password=112159
第三步·在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>
<!-- 指定配置文件 -->
<properties resource="db.properties"/>
<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}" />
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="com/hoppi/mapper/UserMapper.xml" />
</mappers>
</configuration>
非常特殊的一点,
<properties>
标签内可以用<property name="xxx" value="xxx" />
标签来传入键值对,在<dataSource>
中依然可以通过name取出这些value使用,但当与外部配置文件中内容冲突时优先级低于配置文件
另外,如果出现找不到资源文件的问题,在该项目的pom.xml中配置<build>
-><resources>
-><resource>
标签,添加<directory>资源文件所在目录如src/main/resources</directory> <includes> <!-- 根据自己需要配置需导入的文件类型 --> <include>**/*.xml</include> <include>**/*.properties</include> </includes> <filtering>true</filtering>
3. 类型别名
类型别名用于在mapper.xml中消除冗余,比如说在我UserMapper.xml文件中写一个User类用的是包含其路径的,即com.hoppi.pojo.User,我们想要使用它的缩写或类名来代替全名,故产生了这种功能
具体的用法是在Mybatis配置文件的<typeAliases>
下加入<typeAlias type="具体类名" alias="简称"/>
如<typeAlias type="com.hoppi.pojo.User" alias="User"/>
以简称代替全称 或 加入<package name="类所在包名如com.hoppi.pojo"/>
来指定包名以允许不使用全称,第二种方式的默认别名为该包下实体类的名称,首字母大小写皆可,但推荐使用小写以区分类型,想自定义别名可以在该类添加注解@Alias("xxx")
,但弹幕君建议大家最好不要用注解,弹幕有云,注解一时爽,维护火葬场
4. 结果集映射
解决的问题:实体类属性名与对应表字段名不一致
通常用于复杂的sql查询
较为普通的操作是,当实体类属性与表字段名不同且在写Mapper.xml的查询操作需要返回实体类对象时,例如我将User实体类中password属性改名为pwd,再去使用getUserList时。
将返回结果resultType="实体类名"改为resultMap=“Map名”,并编写Map中的映射,例如
<select id="getUserList" resultType="user">
select * from mybatis.users
</select>
—>
<resultMap id="userMap" type="user">
<!-- 只需要编写属性字段名不同的映射 -->
<result column="password" property="pwd"/>
</resultMap>
<select id="getUserList" resultMap="userMap">
select * from mybatis.users
</select>
5. 日志输出
a. 标准日志输出
在mybatis-config.xml中配置setting的logImpl
为STDOUT_LOGGING
,如图:
这样执行的时候就会标准化打印日志了
b. Log4j
配置setting的logImpl
为Log4j
为该项目导入Apache Log4j 1.2
的包或是添加Maven依赖
当然是选择添加依赖啦
在Maven Repository中找到这个包的依赖
<!-- https://mvnrepository.com/artifact/log4j/log4j -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
在pom.xml中添加
之后在resources目录下新建log4j.xml(以前是.properties,现在建议用xml),内容自行百度或CSDNlog4j.xml通用配置
直接复制使用(毕竟小白谁愿意去研究一眼看上去一团屎的东西呢)
参考这篇文章:Log4J.xml配置详解
但是现在能找到的配置都挺老的,这里贴一份修改过的到2021.08.08还能用的配置
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE log4j:configuration SYSTEM "http://logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/xml/doc-files/log4j.dtd">
<log4j:configuration debug="true">
<appender name="myConsole" class="org.apache.log4j.ConsoleAppender">
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern"
value="[%d{dd HH:mm:ss,SSS\} %-5p] [%t] %c{2\} - %m%n"/>
</layout>
<!--过滤器设置输出的级别-->
<filter class="org.apache.log4j.varia.LevelRangeFilter">
<param name="levelMin" value="debug"/>
<param name="levelMax" value="warn"/>
<param name="AcceptOnMatch" value="true"/>
</filter>
</appender>
<appender name="myFile" class="org.apache.log4j.RollingFileAppender">
<!-- 设置日志输出文件名,./代表项目目录 -->
<param name="File" value="./output/output.log"/>
<!-- 设置是否在重新启动服务时,在原有日志的基础添加新日志 -->
<param name="Append" value="false"/>
<param name="MaxBackupIndex" value="10"/>
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="%p (%c:%L)- %m%n"/>
</layout>
</appender>
<!-- 根logger的设置-->
<root>
<priority value="debug"/>
<appender-ref ref="myConsole"/>
<appender-ref ref="myFile"/>
</root>
</log4j:configuration>
在需要输出日志的类中加上
static Logger logger = Logger.getLogger(这个类的类名.class);
注意这里导入的包是org.apache.log4j.Logger
然后可以在此类的方法中通过logger对象输出日志,比如logger.info("提示语")
、logger.debug("提示语")
、logger.error("提示语")
,个人习惯把"提示语"
换成this
,这样可以打印相对路径
当然,只要在mybatis-config.xml中有配置log4j日志输出并且正确配置都会输出日志(部分),注意当取消日志输出的时候要记得删掉或注释掉pom.xml中的log4j依赖,否则会出现报错
log4j:WARN No appenders could be found for logger (org.apache.ibatis.logging.LogFactory).
log4j:WARN Please initialize the log4j system properly.
log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info.
注解代替xml
首先明确一点,注解与xml配置不能同时作用于同一方法
再然后注解主要用于简单的sql语句,对于复杂的sql推荐使用xml配置
注解与xml的语法一模一样,但如果传入的基本类型参数有多个(它可以做到直接传入多个参数而不使用Map),必须在参数前加@Param("映射名")
注释,而引用类型没有说明强制使用,映射名是被sql语句中的#{}调用的名字
举个例子
@Update("update mybatis.users set name=#{name},password=#{password} where id=#{id}")
void update(@Param("id") int user_id, @Param("name") String user_name, @Param("password") String password);
记录一个离谱的关于中文乱码的问题,我到这一步传入中文name都是乱码,所有配置都是正确的,百度CSDN博客园根本查不到,直接气死,后来直接复制一下这个模块的内容到一个新建的模块中,这个bug魔幻地被解决了,初步猜想和缓存有关,我只能说remake永远的神
嵌套查询
像我这种水平比较低的初学者一般用xml来做这种事情
子查询
子查询实际上是按照查询嵌套处理,也就是走了两遍查询
最简单的例子,学生+老师,学生表里有id,name,teacher_id,老师表里有id,name,teacher_id为该学生对应的老师,现在要求用子查询查询所有学生的id,name和对应的老师name
首先,建表,不做赘述
然后,建一个新项目,结构大致如图
当然Mapper.xml可以直接放到java下的mapper包下,我这里放到了resources下,效果是一样的
再然后写一下实体类,注意这里Student类中对应teacher_id的属性是Teacher类对象
写一下Mapper接口的方法,这里由于没有在TeacherMapper中实现任何操作,所以TeacherMapper不用新建,我这里新建是为了好看(振声,查询学生的方法:List<Student> getStudents();
接着配置xml,因为是对student的查询,所有根本不用TeacherMapper.xml,直接上StudentMapper.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.hoppi.mapper.StudentMapper"> <select id="getStudents" resultMap="student_info"> select * from student </select> <resultMap id="student_info" type="Student"> <association property="teacher" column="teacher_id" javaType="Teacher" select="getTeacher"/> </resultMap> <select id="getTeacher" resultType="Teacher"> select * from teacher where id = #{teacher_id} </select> </mapper>
分析标签:
- 第一个
select
的内容是查询学生表的所有字段,返回值是自己定义的类型student_info
resultMap
的内容是自定义student_info,类型为Student类(我在这里使用了别名),association
为一对一复杂对象映射,其中,property
为Student类中的属性名,column
为表中字段,javaType
为该字段/属性对应Java类型,select
为嵌套查询语句,调用下面的getTeacher- 第二个
select
的内容是查询id为传入参数(这里是调用它的getStudents传入的teacher_id,当然由于只传入一个参数,所以名字可以随便取,叫id也行)的老师表的所有字段,返回值是Teacher
写一个测试类跑一下,
package com.hoppi.mapper;
import com.hoppi.pojo.Student;
import com.hoppi.utils.MybatisUtil;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
public class StudentMapperTest {
@Test
public void Teat(){
SqlSession sqlSession = MybatisUtil.getSqlSession();
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
for (Student student : mapper.getStudents()) {
System.out.println("ID:" + student.getId() + "\tName:" + student.getName() + "\tTeacher:" + student.getTeacher().getName());
}
sqlSession.close();
}
}
输出:
联表查询
连表查询实际上是按照结果嵌套处理,真正嵌套的是查询得到的结果
继续用上面那个例子,这次要求联表查询
显然,用sql语句表达就是很简单的一句话select s.id,s.name,t.name from student s,teacher t where s.teacher_id=t.id
实现方式如下:
<?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.hoppi.mapper.StudentMapper"> <select id="getStudents" resultMap="student_info"> select s.id sid,s.name sname,t.name tname from student s,teacher t where s.teacher_id=t.id </select> <resultMap id="student_info" type="Student"> <result property="id" column="sid"/> <result property="name" column="sname"/> <!-- association与result类似,但对象是实体类 --> <association property="teacher" javaType="Teacher"> <result property="name" column="tname"/> </association> </resultMap> </mapper>
分析标签:
select
即联表查询语句,获取到的sid、sname、tname是我们需要的,resultMap
依旧对应自己定义的类型student_info
resultMap
即我们自定义的类型,依然是Student类,其中result
和association
对应Student中的属性,result
中property为属性名,column为查询到的列名,之所以这里需要在sql语句中重命名是因为如果直接用s.id这种形式会无法解析得到对应的值,association
中property同样为属性名,javaType为对应java类型,其中的result
对应该java类型中的属性听起来绕得一批,实际上就是属性的嵌套
多对一与一对多
显然,上面的不管是子查询还是联表查询都是以多对一的关系进行查询,即多个学生对应一个老师,以学生为主体
现在换一个问题,一个老师对应多个学生,要查询所有老师的id、name以及教的学生的id、name
表还是那个表,但pojo类需要做出对应的改变,Student类中teacher属性改为int类型的teacher_id,Teacher类中添加List<Student> students属性
容易看出,这时的查询已经变成了以老师为主体
这里用联表查询的方式来实现一下
来写一下TeacherMapper.xml的select
<select id="getTeachers" resultMap="teacher_info"> select t.id tid,t.name tname,s.id sid,s.name sname from student s,teacher t where s.teacher_id=t.id </select> <resultMap id="teacher_info" type="Teacher"> <result property="id" column="tid" /> <result property="name" column="tname" /> <!-- association也与result类似,但对象是集合 --> <!-- ofType用于指定对象的所属javaBean类,通常用于集合指定的泛型 --> <collection property="students" ofType="Student"> <result property="id" column="sid" /> <result property="name" column="sname" /> <result property="teacher_id" column="tid" /> </collection> </resultMap>
测试类试一下
package com.hoppi.mapper; import com.hoppi.pojo.Student; import com.hoppi.pojo.Teacher; import com.hoppi.utils.MybatisUtil; import org.apache.ibatis.session.SqlSession; import org.junit.Test; public class TeacherMapperTest { @Test public void Test(){ SqlSession sqlSession = MybatisUtil.getSqlSession(); TeacherMapper mapper = sqlSession.getMapper(TeacherMapper.class); for (Teacher teacher : mapper.getTeachers()) { System.out.println("老师姓名:" + teacher.getName() + "\t老师ID:" + teacher.getId() + "\t学生名单:"); for (Student student : teacher.getStudents()) { System.out.println("\t学生姓名:" + student.getName() + "\t学生ID:" + student.getId()); } } sqlSession.close(); } }
运行结果:
同样的,子查询也适用于一对多,xml中select如下
<select id="getTeachers" resultMap="teacher_info"> select * from teacher </select> <resultMap id="teacher_info" type="Teacher"> <!--这里加一个属性与列名同名的result是因为下面的collection对应的列名也是id,不加的话Teacher本身的id就会为0--> <result property="id" column="id" /> <collection property="students" column="id" javaType="ArrayList" ofType="Student" select="getStudents" /> </resultMap> <select id="getStudents" resultType="Student"> select * from student where teacher_id=#{id} </select>
动态SQL
xml标签
常用标签:if, choose, when, otherwise, where, trim, set, foreach
- if即条件判断,若条件为真,则执行标签内的sql语句
示例:
<if text ="条件">
拼接的sql语句
</if>
- choose类似于swich,与when和otherwise一同使用
示例:
select * from 表名 where
<choose>
<when test="条件1">
拼接的sql语句1
</when>
<when test="条件2">
拼接的sql语句2
</when>
<otherwise>
拼接的sql语句3
</otherwise>
</choose>
当条件1为真时,执行语句1,跳过剩余语句;条件1为假时,若条件2为真,执行语句2,跳过剩余语句,以此类推,若所有when都不满足,则执行otherwise中的语句
- where元素只会在子元素返回任何内容的情况下才插入 “WHERE” 子句。而且,若子句的开头为 “AND” 或 “OR”,where 元素也会将它们去除。(文档原话)
示例:
select * from 表名
<where>
<if test="条件1">
拼接的sql语句1
</if>
<if test="条件2">
and 拼接的sql语句2
</if>
</where>
当条件1、2均为真时,执行select * from 表名 where 拼接的sql语句1 and 拼接的sql语句2
;当1为假,2为真时执行select * from 表名 where 拼接的sql语句2
- set用于update,会自动删除拼接的sql语句中多余的逗号
示例:
update 表名
<set>
<if test="条件1">
字段1=传入值1,
</if>
<if test="条件2">
字段2=传入值2,
</if>
<if test="条件3">
字段3=传入值3
</if>
</set>
where 定位语句
其中set的作用为,当条件3为假而其它两个条件不全为假时去除返回sql语句末尾的逗号
- trim起的作用类似于where和set,但它却是可定制的
和 where 元素等价的自定义 trim 元素为:
<trim prefix="WHERE" prefixOverrides="AND |OR "> ... </trim>
prefix即开头自动添加语句,prefixOverrides即开头自动删除语句(如果有的话),注意空格
和set元素等价的自定义trim元素为:
<trim prefix="SET" suffixOverrides=","> ... </trim>
suffixOverrides即末尾自动删除语句,可推测,suffix是末尾自动添加语句
- foreach比较复杂,可以参考一下这篇文章
简单的说明一下↓
如果是
select...where...(or)
的话,那么就应该使用<foreach collection="list" item="item" open="(" separator="or" close=")"> #{item} </foreach>
如果是
select...in...
这种,那么就应该使用<foreach collection="list" item="item" open="(" separator="," close=")"> #{item} </foreach>
如果是
insert into...values...
这种,那么就要使用<!-- 用于list插入多条单字段记录 --> <foreach collection="list" item="item" separator=","> (#{item}) </foreach>
或
<!-- 用于map插入多条多字段记录 --> <foreach collection="map" index="key" item="value" separator=","> (#{key},#{value}) </foreach>
- sql,这是用于实现sql代码复用的标签,当在Mapper.xml中出现大量重复代码时,会影响美观,此时需要用sql标签对其进行封装(需设置id),使用的时候在需要用到的地方通过include标签的refid引用其id即可
注解中的动态SQL
注解中的动态SQL和xml中的相同,区别在于使用<script>标签,官方文档的示例如下:
缓存
Mybatis的缓存分为一级和二级缓存。一级缓存对应且存在于一个SqlSession开启到关闭的过程中,一级缓存默认开启且不可关闭;二级缓存对应一个mapper对象,当SqlSession关闭或提交时,其一级缓存会存到对应mapper的二级缓存中,之后开启此mapper对应sqlSession会直接从二级缓存中读取(如果有的话)
具体的请参考这篇文章
再见
就写到这吧,去水Spring笔记了("▔□▔)/