提供持久层框架包括SQL Maps和Data Access Objects(DAO).
SQL Maps提供数据库数据和java数据的映射关系,换句话说即是封装JDBC的过程。
Data Access Objects数据访问对象,通过JDBC访问数据库然后操作数据库中的数据
1) MyBatis 是支持定制化 SQL、存储过程以及高级映射的优秀的持久层框架
高级映射指的是数据库字段名和java属性名不一致时的映射,相反只能处理相同情况的映射称为普通映射
2) MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集
3) MyBatis可以使用简单的XML或注解用于配置和原始映射,将接口和Java的POJO(Plain Old Java
Objects,普通的Java对象)映射成数据库中的记录
4) MyBatis 是一个 半自动的ORM(Object Relation Mapping)框架
①JDBC
SQL 夹杂在Java代码中耦合度高,导致硬编码内伤
维护不易且实际开发需求中 SQL 有变化,频繁修改的情况多见
代码冗长,开发效率低
②Hibernate 和 JPA
操作简便,开发效率高
程序中的长难复杂 SQL 需要绕过框架
内部自动生产的 SQL,不容易做特殊优化
基于全映射的全自动框架,大量字段的 POJO 进行部分映射时比较困难。
反射操作太多,导致数据库性能下降
③MyBatis
轻量级,性能出色
SQL 和 Java 编码分开,功能边界清晰。Java代码专注业务、SQL语句专注数据
开发效率稍逊于HIbernate,但是完全能够接受
IDE:idea 2020.2
构建工具:maven 3.6.3
MySQL版本:MySQL 5.0.12
MyBatis版本:MyBatis 3.5.7
a>打包方式:jar
b>引入依赖
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-mxj-db-files</artifactId>
<version>5.0.12</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<!-- Mybatis核心 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.7</version>
</dependency>
</dependencies>
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>
<!--配置数据库的环境 -->
<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/mydb"/>
<property name="username" value="root"/>
<property name="password" value="abc123"/>
</dataSource>
</environment>
</environments>
<!--引入映射文件 -->
<mappers>
<mapper resource="org/mybatis/example/BlogMapper.xml"/>
</mappers>
</configuration>
mapper接口相当于以前的DAO,但是区别在于mapper仅仅是接口,不需要提供额外的实现类,因为mybatis里有面向接口编程,每当调用接口中的方法,mybatis就会自动执行相应的sql语句
public interface UserMapper {
int insertUser();
}
<?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.hikaru.mybatis.mapper.UserMapper">
<insert id="insertUser">
insert into user(user, balance) values("2b", 1000)
</insert>
</mapper>
1、映射文件的命名规则:
表所对应的实体类的类名+Mapper.xml
例如:表t_user,映射的实体类为User,所对应的映射文件为UserMapper.xml
因此一个映射文件对应一个实体类,对应一张表的操作
MyBatis映射文件用于编写SQL,访问以及操作表中的数据
MyBatis映射文件存放的位置是src/main/resources/mappers目录下
2、MyBatis中可以面向接口操作数据,要保证两个一致:
a>mapper接口的全类名和映射文件的命名空间(namespace)保持一致
b>mapper接口中方法的方法名和映射文件中编写SQL的标签的id属性保持一致
<?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.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mydb"/>
<property name="username" value="root"/>
<property name="password" value="abc123"/>
</dataSource>
</environment>
</environments>
<!--引入映射文件 -->
<mappers>
<mapper resource="mappers/UserMapper.xml"/>
</mappers>
</configuration>
@Test
public void testMybatis() throws IOException {
//加载核心配置文件
InputStream resourceAsStream = Resources.getResourceAsStream("mybatis-config.xml");
//获取SqlSessionFactoryBuilder
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
//构建SqlSessionFactory
SqlSessionFactory sessionFactory = builder.build(resourceAsStream);
//获取SqlSession
SqlSession sqlSession = sessionFactory.openSession();
//返回一个接口对应的实现类
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
int result = mapper.insertUser();
sqlSession.commit();
System.out.println("result = " + result);
}
SqlSession是一个数据库与java程序的通话,底层实现了mapper的接口实现类
当mapper接口调用方法时,mybatis会到同名配置文件执行被调用方法的相应sql语句
由于mybatis核心配置文件中的
sql的执行本质是事务,因此需要进行事务的提交
SqlSession sqlSession = sessionFactory.openSession(true);
<!-- log4j日志 -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
log4j的配置文件为log4j.xml,文件目录为src/main/resource下
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
<appender name="STDOUT" class="org.apache.log4j.ConsoleAppender">
<param name="Encoding" value="UTF-8" />
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="%-5p %d{MM-dd HH:mm:ss,SSS}
%m (%F:%L) \n" />
</layout>
</appender>
<logger name="java.sql">
<level value="debug" />
</logger>
<logger name="org.apache.ibatis">
<level value="info" />
</logger>
<root>
<level value="debug" />
<appender-ref ref="STDOUT" />
</root>
</log4j:configuration>
日志的级别
FATAL>ERROR>WARN>INFO>DEBUG
从左往右打印的内容会越来越详细
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">
<mapper namespace="com.hikaru.mybatis.mapper.UserMapper">
<insert id="insertUser">
insert into user(user, balance) values("2b", 1000)
</insert>
<select id="getUserById" resultType="com.hikaru.mybatis.pojo.User">
select * from user where id = 3
</select>
<select id="getAllUser" resultType="com.hikaru.mybatis.pojo.User">
select * from user;
</select>
</mapper>
查询功能必须设置resultType或resultMap
resultType:设置默认的映射关系,要求数据库字段名与实体属性名一致
resultMap:设置自定义的映射关系
Mapper接口
public interface UserMapper {
int insertUser();
User getUserById();
List<User> getAllUser();
}
测试
@Test
public void testMybatis() throws IOException {
//加载核心配置文件
InputStream resourceAsStream = Resources.getResourceAsStream("mybatis-config.xml");
//获取SqlSessionFactoryBuilder
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
//构建SqlSessionFactory
SqlSessionFactory sessionFactory = builder.build(resourceAsStream);
//获取SqlSession
SqlSession sqlSession = sessionFactory.openSession(true);
//返回一个接口对应的实现类
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user = mapper.getUserById();
System.out.println(user);
List<User> users =mapper.getAllUser();
users.forEach(user1 -> System.out.println(user1));
}
核心配置文件中的标签必须按照固定的顺序:
properties?,settings?,typeAliases?,typeHandlers?,objectFactory?,objectWrapperFactory?,reflectorFactory?,plugins?,environments?,databaseIdProvider?,mappers?
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>
<!--配置数据库的环境 -->
<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://localhost:3306/mydb"/>
<property name="username" value="root"/>
<property name="password" value="abc123"/>
</dataSource>
</environment>
</environments>
<!--引入映射文件 -->
<mappers>
<mapper resource="mappers/UserMapper.xml"/>
</mappers>
</configuration>
enviroments
default:表示存在多个数据库环境时,默认使用的数据库环境id
enviroment
①id:数据库环境的唯一表示
②transactionManager:设置事务管理方式。
属性:type=“JDBC”或者“MANAGED”。JDBC表示是原生的事务管理方式,事物的提交和回滚都需要手动进行。MANAGED:被管理,例如Spring
③dataSource:配置数据源
属性type=“POOLED”或者“UNPOOLED”或者“JNDI”。POOLED表示数据库连接池,UNPOOLED表示不使用数据库连接池,JNDI表示使用上下文的数据源
在Spring整合后,可以直接使用Spring的数据源
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mydb
jdbc.username=root
jdbc.password=abc123
在配置文件中使用
<properties resource="jdbc.properties"/>
<!--配置数据库的环境 -->
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</dataSource>
</environment>
</environments>
<typeAliases >
<typeAlias type="com.hikaru.mybatis.pojo.User" alias="user"></typeAlias>
</typeAliases>
属性type为被添加别名的类型,alias为添加的别名(不区分大小写)
如没有声明alias属性,则默认别名为类名(仍不区分大小写)
则在mybatis的范围内使用user及其大小写可以替换完整类名
<?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.hikaru.mybatis.mapper.UserMapper">
<insert id="insertUser">
insert into user(user, balance) values("2b", 1000)
</insert>
<select id="getUserById" resultType="user">
select * from user where id = 3
</select>
<select id="getAllUser" resultType="User">
select * from user;
</select>
</mapper>
也可以使用标签aliases下的标签package,替换一个包内的所有类型名为默认别名
<typeAliases >
<package name="com.hikaru.mybatis.pojo"/>
</typeAliases>
<!--引入映射文件 -->
<mappers>
<!-- <mapper resource="mappers/UserMapper.xml"/>-->
<package name="com.hikaru.mybatis.mapper"/>
</mappers>
要求映射文件的包名要和mapper接口所在包名一致
映射文件名和mapper接口名一致
资源下的包名不能用.要用/
若mapper接口中的方法参数为单个的字面量类型
此时可以使用${}和#{}以任意的名称获取参数的值,注意${}需要手动加单引号
<!--select * from user where user = #{aaa}-->
select * from user where user = '${aaa}'
Mapper接口:
int insertUserByNameAndBalance(String user, double balance);
Mapper映射文件:
<insert id="insertUserByNameAndBalance">
insert into user(user, balance) values (#{param1}, #{arg1})
</insert>
若mapper接口中的方法参数为多个时
此时MyBatis会自动将这些参数放在一个map集合中,以arg0,arg1…为键,以参数为值;以param1,param2…为键,以参数为值; 因此只需要通过${}和#{}访问map集合的键就可以获取相对应的值,注意${}需要手动加单引号
若mapper接口中的方法需要的参数为多个时,此时可以手动创建map集合,将这些数据放在map中只需要通过${}和#{}访问map集合的键就可以获取相对应的值,注意${}需要手动加单引号
mapper接口:
int insertUserByNameAndBalance(Map<String, Object> map);
mapper映射文件:
<insert id="insertUserByNameAndBalance">
insert into user(user, balance) values (#{user}, #{balance})
</insert>
这时原来自定义的键值param和arg都不能再用了
若mapper接口中的方法参数为实体类对象时
此时可以使用${}和#{},通过访问实体类对象中的属性名获取属性值,注意${}需要手动加单引号
mapper接口
int insertUser(User user);
mapper映射文件
<insert id="insertUser">
insert into user(user, balance) values(#{user}, #{balance})
</insert>
这里的属性名狭义上讲是实体类get、set方法的名称得到小写,而不是成员变量,要严格对应get、set方法的名称
以通过@Param注解标识mapper接口中的方法参数
此时,会将这些参数放在map集合中,以@Param注解的value属性值为键,以参数为值;以
param1,param2…为键,以参数为值;只需要通过${}和#{}访问map集合的键就可以获取相对应的值,注意${}需要手动加单引号
mapper接口
int insertUser(@Param("username") String username, @Param("balance") double balance);
mapper映射文件
<insert id="insertUser">
insert into user(user, balance) values(#{username}, #{balance})
</insert>
<insert id="insertUser">
insert into user(user, balance) values(#{param1}, #{param2})
</insert>
这里相较于③map集合的方式,保留了param的键值对
查询出的数据只有一条时,可以通过 ①实体类②list集合③map集合 接收
map集合形式为:{balance=1000, id=1, user=2b},这种方法方便将数据转换为json的形式
查询的数据有多条时,一定不能通过实体类对象接收,否则会报TooManyResultException错误
mapper接口
int getCount();
mapper映射文件
<select id="getCount" resultType="int">
select count(*) from user;
</select>
Alias Mapped Type
_byte byte
_long long
_short short
_int int
_integer int
_double double
_float float
_boolean boolean
string String
byte Byte
long Long
short Short
int Integer
integer Integer
double Double
float Float
boolean Boolean
date Date
decimal BigDecimal
bigdecimal BigDecimal
object Object
map Map
hashmap HashMap
list List
arraylist ArrayList
collection Collection
iterator Iteratot
collection
iterator
Mybatis内置的别名
map接口:
Map<String, Object> getUserIdToMap(@Param("id") int id);
map映射文件:
<select id="getUserIdToMap" resultType="map">
select *
from user
where id = #{id};
</select>
①通过List
mapper接口
List<Map<String, Object>> list = mapper.getUserIdToMap();
mapper配置文件
<select id="getUserIdToMap" resultType="map">
select *
from user;
</select>
结果为map类型的list集合:
[{balance=1000, id=1, user=2b}, {balance=1000, id=2, user=9s}, {balance=1000, id=3, user=9s},
②通过@MapKey()的形式
mapper接口
@MapKey("id")
Map<String, Object> getUserIdToMap();
mapper映射文件
<select id="getUserIdToMap" resultType="map">
select *
from user;
</select>
结果为map类型的map,键为多个map获取的某一属性值,值为获取的多个map集合:
{1={balance=1000, id=1, user=2b}, 2={balance=1000, id=2, user=9s}, 3={balance=1000, id=3, user=9s},
mapper接口
int deleteMore(@Param("ids") String ids);
mapper映射文件
<delete id="deleteMore">
delete
from user
where id in (${ids})
</delete>
in批量删除只能使用$,#的话会自动添加双引号导致sql语句不正确
mapper接口
List<User> seleceByTableName(@Param("tableName") String tableName);
mapper配置文件
<select id="seleceByTableName" resultType="user">
select *
from ${tableName}
只能使用${}
mapper接口
int deleteMore(@Param("ids") String ids);
mapper配置文件
<insert id="insertUser" useGeneratedKeys="true" keyProperty="id">
insert into user(user, balance) values(#{user}, #{balance})
</insert>
userGeneratedKeys设置当前标签中的sql是否使用了主键自增
keyProperty将自增的主键赋值给传输到配置文件的参数的某个属性
test
@Test
public void testMybatis() throws IOException {
SqlSession sqlSession = SqlSessionUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user = new User("ryuu", 10000, null);
mapper.insertUser(user);
System.out.println(user);
}
输出
User{user='ryuu', balance=10000.0, id=14}
”对一对应对象,对多对应集合“
字段名和属性名不一致的输出
Emp{eid=1, empName='null', sex='男', email='123@qq.com', did=1}
Emp{eid=2, empName='null', sex='女', email='123@qq.com', did=2}
Emp{eid=3, empName='null', sex='男', email='123@qq.com', did=3}
Emp{eid=4, empName='null', sex='女', email='123@qq.com', did=1}
Emp{eid=5, empName='null', sex='男', email='123@qq.com', did=2}
mapUnderscoreToCamelCase表示将数据库字段名_映射为java驼峰式属性名,解决字段名和属性名的映射关系
<settings>
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
mapper接口
List<Emp> getEmpAndDept(String eid);
mapper映射文件
<resultMap id="empAndDeptResultMap1" type="emp">
<id property="eid" column="eid"></id>
<result property="empName" column="emp_name"></result>
<result property="age" column="age"></result>
<result property="sex" column="sex"></result>
<result property="email" column="email"></result>
<result property="dept.did" column="did"></result>
<result property="dept.deptName" column="dept_name"></result>
</resultMap>
<select id="getEmpAndDept" resultMap="empAndDeptResultMap1">
select * from t_emp left join t_dept on t_emp.did = t_dept.did where t_emp.eid = #{eid};
</select>
测试结果
Emp{eid=2, empName='2B', sex='女', email='123@qq.com', age='22', dept=Dept{did=2, deptName='B'}}
mapper接口
List<Emp> getEmpAndDept2(String eid);
mapper配置文件
<resultMap id="empAndDeptResultMap2" type="emp">
<id property="eid" column="eid"></id>
<result property="empName" column="emp_name"></result>
<result property="age" column="age"></result>
<result property="sex" column="sex"></result>
<result property="email" column="email"></result>
<association property="dept" javaType="dept">
<id property="did" column="did"></id>
<result property="deptName" column="dept_name"></result>
</association>
</resultMap>
<select id="getEmpAndDept2" resultMap="empAndDeptResultMap2">
select * from t_emp left join t_dept on t_emp.did = t_dept.did where t_emp.eid = #{eid};
</select>
测试结果
Emp{eid=2, empName='2B', sex='女', email='123@qq.com', age='22', dept=Dept{did=2, deptName='B'}}
mapper接口
List<Emp> getEmpAndDeptStep1(@Param("eid") String eid);
Dept getEmpAndDeptStep2(String did);
mapper映射文件
<resultMap id="empAndDeptResultMap" type="emp">
<id property="eid" column="eid"></id>
<result property="empName" column="emp_name"></result>
<result property="age" column="age"></result>
<result property="sex" column="sex"></result>
<result property="email" column="email"></result>
<association property="dept"
select="com.hikaru.mybatis.mapper.DeptMapper.getEmpAndDeptStep2"
column="did">
</association>
</resultMap>
<select id="getEmpAndDeptStep1" resultMap="empAndDeptResultMap">
select * from t_emp where eid = #{eid};
</select>
select表示调用其他表对应的映射接口的方法
<select id="getEmpAndDeptStep2" resultType="dept">
select * from t_dept where did = #{did};
</select>
这里没有对dept映射做ResultMap处理,是因为mybatis核心配置中开启了下划线到驼峰的自动映射
结果
Emp{eid=2, empName='2B', sex='女', email='123@qq.com', age='22', dept=Dept{did=2, deptName='B'}}
<settings>
<setting name="mapUnderscoreToCamelCase" value="true"/>
<setting name="lazyLoadingEnabled" value="true"/>
</settings>
理解延迟加载:并不是开启后只会执行第一步的查询而不执行第二步,而是需要执行第二步的时候才去执行第二步
测试①:
Emp emp = empMapper.getEmpAndDeptStep1("2");
System.out.println(emp.getEmpName());
输出:
DEBUG 05-04 13:04:48,410 ==> Preparing: select * from t_emp where eid = ?; (BaseJdbcLogger.java:137)
DEBUG 05-04 13:04:48,437 ==> Parameters: 2(String) (BaseJdbcLogger.java:137)
DEBUG 05-04 13:04:48,495 <== Total: 1 (BaseJdbcLogger.java:137)
2B
测试②:
Emp emp = empMapper.getEmpAndDeptStep1("2");
System.out.println(emp.getDept());
输出:
DEBUG 05-04 13:06:45,414 ==> Preparing: select * from t_emp where eid = ?; (BaseJdbcLogger.java:137)
DEBUG 05-04 13:06:45,446 ==> Parameters: 2(String) (BaseJdbcLogger.java:137)
DEBUG 05-04 13:06:45,567 <== Total: 1 (BaseJdbcLogger.java:137)
DEBUG 05-04 13:06:45,569 ==> Preparing: select * from t_dept where did = ?; (BaseJdbcLogger.java:137)
DEBUG 05-04 13:06:45,569 ==> Parameters: 2(Integer) (BaseJdbcLogger.java:137)
DEBUG 05-04 13:06:45,571 <== Total: 1 (BaseJdbcLogger.java:137)
Dept{did=2, deptName='B'}
如上第二组测试中需要查询第二步SQL的时候,延迟加载才会去执行第二步
fetchType值为eager或者lazy
<association property="dept"
select="com.hikaru.mybatis.mapper.DeptMapper.getEmpAndDeptStep2"
column="did"
fetchType="eager">
</association>
测试输出:
DEBUG 05-04 13:11:48,340 ==> Preparing: select * from t_emp where eid = ?; (BaseJdbcLogger.java:137)
DEBUG 05-04 13:11:48,365 ==> Parameters: 2(String) (BaseJdbcLogger.java:137)
DEBUG 05-04 13:11:48,383 ====> Preparing: select * from t_dept where did = ?; (BaseJdbcLogger.java:137)
DEBUG 05-04 13:11:48,384 ====> Parameters: 2(Integer) (BaseJdbcLogger.java:137)
DEBUG 05-04 13:11:48,385 <==== Total: 1 (BaseJdbcLogger.java:137)
DEBUG 05-04 13:11:48,387 <== Total: 1 (BaseJdbcLogger.java:137)
2B
”对一对应对象,对多对应集合“
mapper接口
Dept getDeptAndEmp(@Param("did") String did);
mapper映射文件
<resultMap id="deptAndEmpResultMap" type="dept">
<id property="did" column="did"></id>
<result property="deptName" column="dept_name"></result>
<collection property="emps" ofType="emp">
<id property="eid" column="eid"></id>
<result property="empName" column="emp_name"></result>
<result property="age" column="age"></result>
<result property="sex" column="sex"></result>
<result property="email" column="email"></result>
</collection>
</resultMap>
<select id="getDeptAndEmp" resultMap="deptAndEmpResultMap">
select * from t_dept left join t_emp on t_dept.did = t_emp.did where t_dept.did = #{did};
</select>
collection标签用于解决多对一的映射关系,与association需要javatype获取对象类型不同,collection需要ofType获取集合内的对象类型
集合内对象不能再声明Dept类,防止套娃
mapper接口
Dept getDeptAndEMmpStep1(@Param("did") String did);
List<Emp> getDeptAndEmpStep2(@Param("did") String did);
mapper映射文件
<resultMap id="deptAndEMmpStep1RS" type="dept">
<id property="did" column="did"></id>
<id property="deptName" column="dept_name"></id>
<collection property="emps"
select="com.hikaru.mybatis.mapper.EmpMapper.getDeptAndEmpStep2"
column="did"></collection>
</resultMap>
<select id="getDeptAndEMmpStep1" resultMap="deptAndEMmpStep1RS">
select * from t_dept where did = #{did}
</select>
<select id="getDeptAndEmpStep2" resultType="emp">
select *
from t_emp where did = #{did};
</select>
Mybatis框架的动态SQL技术是一种根据条件动态拼装SQL语句的功能,它存在的意义是为了解决动态拼接SQL语句字符串的痛点问题。
mapper接口
Emp getEmpByCondition(Emp emp);
mapper映射文件
<select id="getEmpByCondition" resultType="emp">
select * from t_emp where 1 = 1
<if test="empName != null and empName != ''">
and emp_name = #{empName}
</if>
<if test="sex != null and sex != ''">
and sex = #{sex}
</if>
<if test="email != null and email != ''">
and email = #{email}
</if>
<if test="age != null and age != ''">
and age = #{age}
</if>
</select>
使用恒成立条件1=1防止sql语句and前缀错误
测试输出
DEBUG 05-05 09:11:48,799 ==> Preparing: select * from t_emp where 1 = 1 and emp_name = ? and sex = ? and email = ? (BaseJdbcLogger.java:137)
DEBUG 05-05 09:11:48,824 ==> Parameters: 1A(String), 男(String), 123@qq.com(String) (BaseJdbcLogger.java:137)
DEBUG 05-05 09:11:48,847 <== Total: 1 (BaseJdbcLogger.java:137)
Emp{eid=1, empName='1A', sex='男', email='123@qq.com', age='23', dept=null}
Process finished with exit code 0
mapper映射文件
<select id="getEmpByCondition" resultType="emp">
select * from t_emp
<where>
<if test="empName != null and empName != ''">
and emp_name = #{empName}
</if>
<if test="sex != null and sex != ''">
and sex = #{sex}
</if>
<if test="email != null and email != ''">
and email = #{email}
</if>
<if test="age != null and age != ''">
and age = #{age}
</if>
</where>
</select>
当where标签中有内容时,会自动生成where关键字,并将内容前多余的and、or去掉
当where标签中没有内容时,where标签不会生成where关键字
where标签不能将内容后面的and、or去掉
trim若标签中有内容时:
prefix|suffix:将trim标签中内容前面或者后面添加指定的内容
prefixOverrides|suffixOverrides:将trim标签中内容前面或者后面去掉指定的内容
若标签中没有内容:trim标签也没有任何的效果
mapper映射文件
<select id="getEmpByCondition" resultType="emp">
select * from t_emp
<trim prefix="where" suffixOverrides="and">
<if test="empName != null and empName != ''">
emp_name = #{empName} and
</if>
<if test="sex != null and sex != ''">
sex = #{sex} and
</if>
<if test="email != null and email != ''">
email = #{email} and
</if>
<if test="age != null and age != ''">
age = #{age} and
</if>
</trim>
</select>
输出
DEBUG 05-05 09:36:20,141 ==> Preparing: select * from t_emp (BaseJdbcLogger.java:137)
DEBUG 05-05 09:36:20,172 ==> Parameters: (BaseJdbcLogger.java:137)
DEBUG 05-05 09:36:20,196 <== Total: 5 (BaseJdbcLogger.java:137)
Emp{eid=1, empName='1A', sex='男', email='123@qq.com', age='23', dept=null}
Emp{eid=2, empName='2B', sex='女', email='123@qq.com', age='22', dept=null}
Emp{eid=3, empName='3S', sex='男', email='123@qq.com', age='23', dept=null}
Emp{eid=4, empName='9S', sex='女', email='123@qq.com', age='24', dept=null}
Emp{eid=5, empName='1T', sex='男', email='123@qq.com', age='22', dept=null}
when、otherwise相当于if if if … else
属性collection表示需要循环的数组或集合,item为数组或集合中的每一个数据
separator表示循环体之间的分隔符
open|close可以在标签中的内容的开始和结束添加指定内容
mapper接口
int deleteMore(@Param("eids") Integer[] ids);
mapper映射文件
<delete id="deleteMore">
delete from t_emp where eid in
<foreach collection="eids" separator="," item="eid" open="(" close=")">
#{eid}
</foreach>
</delete>
测试输出
DEBUG 05-05 10:11:59,303 ==> Preparing: delete from t_emp where eid in ( ? , ? ) (BaseJdbcLogger.java:137)
DEBUG 05-05 10:11:59,329 ==> Parameters: 6(Integer), 7(Integer) (BaseJdbcLogger.java:137)
DEBUG 05-05 10:11:59,331 <== Updates: 0 (BaseJdbcLogger.java:137)
mapper接口
int insertMore(@Param("emps") List<Emp> emps);
mapper配置文件
<insert id="insertMore">
insert into t_emp (<include refid="empColumns"/>) values
<foreach collection="emps" item="emp" separator=",">
(null, #{emp.empName}, #{emp.sex}, #{emp.email}, 1)
</foreach>
</insert>
这里的include是引用sql片段
设置sql片段
<sql id="empColumns">eid, emp_name, sex, email, did</sql>
测试
@Test
public void testDynamicSQLMapper() {
SqlSession sqlSession = SqlSessionUtils.getSqlSession();
DynamicSQLMapper mapper = sqlSession.getMapper(DynamicSQLMapper.class)
//Integer eid, String empName, String sex, String email, Integer did
Emp emp1 = new Emp(null, "a1", "男", "123@qq.com");
Emp emp2 = new Emp(null, "a2", "男", "123@qq.com");
Emp emp3 = new Emp(null, "a3", "男", "123@qq.com");
List<Emp> emps = Arrays.asList(emp1, emp2, emp3);
int result = mapper.insertMore(emps);
System.out.println(result);
}
缓存只适用于查询
一级缓存是sqlSession级别,自动开启
令一级缓存失效的情况:
①不同的SqlSession对应不同的一级缓存
②同一个SqlSession但是查询条件不同
③同一个SqlSession两次查询期间执行了任何一次增删改操作
④同一个SqlSession两次查询期间手动清空了缓存,sqlsession.clearCache()
测试
SqlSession sqlSession = SqlSessionUtils.getSqlSession();
CacheMapper mapper = sqlSession.getMapper(CacheMapper.class);
List<Emp> emps = mapper.getAll();
emps.forEach(emp -> System.out.println(emp));
SqlSession sqlSession1 = SqlSessionUtils.getSqlSession();
CacheMapper mapper1 = sqlSession1.getMapper(CacheMapper.class);
List<Emp> emps1 = mapper1.getAll();
emps1.forEach(emp -> System.out.println(emp));
结果为两次sql查询
DEBUG 05-05 11:20:12,191 ==> Preparing: select * from t_emp; (BaseJdbcLogger.java:137)
DEBUG 05-05 11:20:12,213 ==> Parameters: (BaseJdbcLogger.java:137)
DEBUG 05-05 11:20:12,231 <== Total: 11 (BaseJdbcLogger.java:137
DEBUG 05-05 11:20:12,297 ==> Preparing: select * from t_emp; (BaseJdbcLogger.java:137)
DEBUG 05-05 11:20:12,297 ==> Parameters: (BaseJdbcLogger.java:137)
DEBUG 05-05 11:20:12,300 <== Total: 11 (BaseJdbcLogger.java:137) )
二级缓存是SqlSessionFactory级别,通过同一个SqlSessionFactory创建的SqlSession查询(即使SqlSession不同)的结果会被缓存;此后若再次执行相同的查询语句,结果就会从缓存中获取
二级缓存开启的条件:
a> 在核心配置文件中,设置全局配置属性cacheEnabled="true",默认为true,不需要设置
<settings>
<setting name="mapUnderscoreToCamelCase" value="true"/>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="cacheEnabled" value="true"/>
</settings>
b> 在mapper映射文件中设置标签cache
<cache />
c> 二级缓存必须在SqlSession关闭或提交之后有效
d> 查询的数据所转换的实体类类型必须实现序列化的接口
即是使实体类继承Serializable接口
使二级缓存失效的情况:
两次查询之间执行了任意的增删改,会使一级和二级缓存同时失效
测试
@Test
public void testCacheTwo() throws IOException {
InputStream resourceAsStream = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory sqlSessionFactory = builder.build(resourceAsStream);
SqlSession sqlSession1 = sqlSessionFactory.openSession(true);
SqlSession sqlSession2 = sqlSessionFactory.openSession(true);
CacheMapper mapper1 = sqlSession1.getMapper(CacheMapper.class);
List<Emp> emps1 = mapper1.getAll();
emps1.forEach(emp -> System.out.println(emp));
sqlSession1.close();
CacheMapper mapper2 = sqlSession2.getMapper(CacheMapper.class);
List<Emp> emps2 = mapper2.getAll();
emps2.forEach(emp -> System.out.println(emp));
sqlSession2.close();
}
测试结果:
DEBUG 05-06 10:30:39,916 Cache Hit Ratio [com.hikaru.mybatis.mapper.CacheMapper]: 0.0 (LoggingCache.java:60)
DEBUG 05-06 10:30:40,800 ==> Preparing: select * from t_emp; (BaseJdbcLogger.java:137)
DEBUG 05-06 10:30:40,822 ==> Parameters: (BaseJdbcLogger.java:137)
DEBUG 05-06 10:30:40,846 <== Total: 11 (BaseJdbcLogger.java:13
7)
DEBUG 05-06 10:34:31,559 Cache Hit Ratio [com.hikaru.mybatis.mapper.CacheMapper]: 0.5 (LoggingCache.java:60)
可以看到只查询了一次SQL,第二次缓存利用率为0.5
eviction属性:缓存回收策略
缓存是缓存到内存中的,故不能无限制地缓存,因此需要设定策略定期清除
LRU(Least Recently Used) – 最近最少使用的:移除最长时间不被使用的对象。
FIFO(First in First out) – 先进先出:按对象进入缓存的顺序来移除它们。
SOFT – 软引用:移除基于垃圾回收器状态和软引用规则的对象。
WEAK – 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。
默认的是 LRU。
flushInterval属性:刷新间隔,单位毫秒
默认情况是不设置,也就是没有刷新间隔,缓存仅仅调用增删改语句时刷新
size属性:引用数目,正整数
代表缓存最多可以存储多少个对象,太大容易导致内存溢出
readOnly属性:只读,true/false
true:只读缓存;会给所有调用者返回缓存对象的相同实例。因此这些对象不能被修改。这提供了很重要的性能优势。
false:读写缓存;会返回缓存对象的拷贝(通过序列化)。这会慢一些,但是安全,因此默认是false。
先查询二级缓存,因为二级缓存中可能会有其他程序已经查出来的数据(因为二级缓存对应多个SQLSession,范围更大),可以拿来直接使用。
如果二级缓存没有命中,再查询一级缓存
如果一级缓存也没有命中,则查询数据库
SqlSession关闭之后,一级缓存中的数据会写入二级缓存
a>添加依赖
<!-- Mybatis EHCache整合包 -->
<dependency>
<groupId>org.mybatis.caches</groupId>
<artifactId>mybatis-ehcache</artifactId>
<version>1.2.1</version>
</dependency>
<!-- slf4j日志门面的一个具体实现 -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
b>各jar包功能
jar包名称 作用
mybatis-ehcache Mybatis和EHCache的整合包
ehcache EHCache核心包
slf4j-api SLF4J 日志门面包
logback-classic 支持SLF4J门面接口的一个具体实
c>创建EHCache的配置文件ehcache.xml
<?xml version="1.0" encoding="utf-8" ?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="../config/ehcache.xsd">
<!-- 磁盘保存路径 -->
<diskStore path="D:\atguigu\ehcache"/>
<defaultCache
maxElementsInMemory="1000"
maxElementsOnDisk="10000000"
eternal="false"
overflowToDisk="true"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU">
</defaultCache>
</ehcache>
d>设置二级缓存的类型
<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>
e>加入logback日志
存在SLF4J时,作为简易日志的log4j将失效,此时我们需要借助SLF4J的具体实现logback来打印日志。
创建logback的配置文件logback.xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="true">
<!-- 指定日志输出的位置 -->
<appender name="STDOUT"
class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<!-- 日志输出的格式 -->
<!-- 按照顺序分别是:时间、日志级别、线程名称、打印日志的类、日志主体内容、换行 -
->
<pattern>[%d{HH:mm:ss.SSS}] [%-5level] [%thread] [%logger]
[%msg]%n</pattern>
</encoder>
</appender>
<!-- 设置全局日志级别。日志级别按顺序分别是:DEBUG、INFO、WARN、ERROR -->
<!-- 指定任何一个日志级别都只打印当前级别和后面级别的日志。 -->
<root level="DEBUG">
<!-- 指定打印日志的appender,这里通过“STDOUT”引用了前面配置的appender -->
<appender-ref ref="STDOUT" />
</root>
<!-- 根据特殊需求指定局部日志级别 -->
<logger name="com.atguigu.crowd.mapper" level="DEBUG"/>
</configuration>
f>EHCache配置文件说明
![](file://D:\资料\学习笔记\Java\Mybatis\1.png?msec=1651885414653)
正向工程: 先创建实体类,再由框架生成数据库表。Hibernate支持正向工程
逆向工程: 先创建数据库表,然后由框架生成下面的资源:
①实体类
②Mapper接口
③Mapper映射文件
a 添加依赖和插件
<!-- 控制Maven在构建过程中相关配置 -->
<build>
<!-- 构建过程中用到的插件 -->
<plugins>
<!-- 具体插件,逆向工程的操作是以构建过程中插件形式出现的 -->
<plugin>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-maven-plugin</artifactId>
<version>1.3.0</version>
<!-- 插件的依赖 -->
<dependencies>
<!-- 逆向工程的核心依赖 -->
<dependency>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-core</artifactId>
<version>1.3.2</version>
</dependency>
<!-- 数据库连接池 -->
<dependency>
<groupId>com.mchange</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.2</version>
</dependency>
<!-- MySQL驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.16</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
b 创建MyBatis核心文件
c 创建逆向工程的配置文件 generatorConfig.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
"http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
<!--
targetRuntime: 执行生成的逆向工程的版本
MyBatis3Simple: 生成基本的CRUD(清新简洁版)
MyBatis3: 生成带条件的CRUD(奢华尊享版)
-->
<context id="DB2Tables" targetRuntime="MyBatis3Simple">
<!-- 数据库的连接信息 -->
<jdbcConnection driverClass="com.mysql.jdbc.Driver"
connectionURL="jdbc:mysql://localhost:3306/mydb"
userId="root"
password="abc123">
</jdbcConnection>
<!-- javaBean的生成策略-->
<javaModelGenerator targetPackage="com.hikaru.mybatis.pojo"
targetProject=".\src\main\java">
<property name="enableSubPackages" value="true" />
<property name="trimStrings" value="true" />
</javaModelGenerator>
<!-- SQL映射文件的生成策略 -->
<sqlMapGenerator targetPackage="com.hikaru.mybatis.mapper"
targetProject=".\src\main\resources">
<property name="enableSubPackages" value="true" />
</sqlMapGenerator>
<!-- Mapper接口的生成策略 -->
<javaClientGenerator type="XMLMAPPER"
targetPackage="com.hikaru.mybatis.mapper" targetProject=".\src\main\java">
<property name="enableSubPackages" value="true" />
</javaClientGenerator>
<!-- 逆向分析的表 -->
<!-- tableName设置为*号,可以对应所有表,此时不写domainObjectName -->
<!-- domainObjectName属性指定生成出来的实体类的类名 -->
<table tableName="t_emp" domainObjectName="Emp"/>
<table tableName="t_dept" domainObjectName="Dept"/>
</context>
</generatorConfiguration>
其中enableSubPackages属性声明是否使用子包(设为false的话,com.hikaru.mybatis.pojo就会被当成一整个路径名而不是多个)
trimStrings属性可以将数据库字段名前后的空格去掉,方便生成资源
d>执行MBG插件的generate目标
![](file://D:\资料\学习笔记\Java\Mybatis\2.png?msec=1651885414566)
测试
DEBUG 05-06 12:23:44,119 ==> Preparing: select eid, emp_name, age, sex, email, did from t_emp (BaseJdbcLogger.java:137)
DEBUG 05-06 12:23:44,143 ==> Parameters: (BaseJdbcLogger.java:137)
DEBUG 05-06 12:23:44,167 <== Total: 11 (BaseJdbcLogger.java:137)
Emp{eid=1, empName='1A', age='23', sex='男', email='123@qq.com', did=1}
Emp{eid=2, empName='2B', age='22', sex='女', email='123@qq.com', did=2}
在执行过程中出现了数据库连接文件JBDC.properties以及log4j无法加载的问题,找了半天错,原来是target文件中没有这两个文件,在Maven中重新clean compile一下即可
a 修改逆向工程核心配置文件的版本号
<context id="DB2Tables" targetRuntime="MyBatis3">
b 生成实体类Example,用于条件CURD 其余与Simple版本相同
c
@Test
public void testGenerate() throws IOException {
InputStream is = Resources.getResourceAsStream("mybatis-config.
SqlSessionFactory sqlFactory = new SqlSessionFactoryBuilder().b
SqlSession sqlSession = sqlFactory.openSession(true);
EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);
// List<Emp> emps = mapper.selectByExample(null);
// emps.forEach(emp -> System.out.println(emp));
EmpExample empExample = new EmpExample();
empExample.createCriteria().andAgeLike("%21%");
empExample.or().andEidEqualTo(3);
empExample.or().andEidEqualTo(4);
List<Emp> emps = mapper.selectByExample(empExample);
emps.forEach(emp -> System.out.println(emp));
}
输出SQL语句
Preparing: select eid, emp_name, age, sex, email, did from t_emp WHERE ( age like ? ) or( eid = ? ) or( eid = ? ) (BaseJdbcLogger.java:137)
Parameters: %21%(String), 3(Integer), 4(Integer) (BaseJdbcLogger.java:137)
Total: 2 (BaseJdbcLogger.java:137) 7)
@Test
public void testGenerate2() throws IOException {
InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory sqlFactory = new SqlSessionFactoryBuilder().build(is);
SqlSession sqlSession = sqlFactory.openSession(true);
EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);
// (Integer eid, String empName, String age, String sex, String email, Integer did) {
// int result = mapper.updateByPrimaryKey(new Emp(1, "1A", null, "男", "456@qq.com", 2));
int result = mapper.updateByPrimaryKeySelective(new Emp(1, "1A", null, "男", "456@qq.com", 2));
System.out.println(result);
}
直接修改会将null也跟新到数据库字段中,而selective选择性修改碰到null则不会对数据库字段做任何修改
手机扫一扫
移动阅读更方便
你可能感兴趣的文章