<dependencies>
<dependency>
<groupId>org.mybatisgroupId>
<artifactId>mybatisartifactId>
<version>3.5.3version>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>5.1.47version>
dependency>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>4.12version>
dependency>
dependencies>
<build>
<resources>
<resource>
<directory>src/main/javadirectory>
<includes>
<include>**/*.propertiesinclude>
<include>**/*.xmlinclude>
includes>
<filtering>falsefiltering>
resource>
resources>
build>
<configuration>
<environments default="dev">
<environment id="dev">
<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&useSSL=false"/>
<property name="username" value="root"/>
<property name="password" value="admin"/>
dataSource>
environment>
environments>
configuration>
CREATE TABLE `user` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`username` varchar(255) DEFAULT NULL,
`password` varchar(255) DEFAULT NULL,
`age` tinyint(1) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8;
public class User {
private int id;
private String username;
private String password;
private int age;
getter()...
setter()...
tostring()...
}
<mapper namespace="com.yauyukbiu.demo.dao.UserDao">
<select id="getAll" resultType="com.yauyukbiu.demo.entity.User">
select * from user
select>
mapper>
<mappers>
<mapper resource="com/yauyukbiu/demo/dao/UserMapper.xml"/>
mappers>
public class MyBatisUtils {
private static SqlSessionFactory sqlSessionFactory;
static {
String resource = "mybatis-config.xml";
InputStream inputStream = null;
try {
//加载xml配置文件
inputStream = Resources.getResourceAsStream(resource);
} catch (IOException e) {
e.printStackTrace();
}
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
}
//封装获取SqlSession
public static SqlSession getSqlSession() {
return sqlSessionFactory.openSession();
}
}
public interface UserDao {
List<User> getAll();
}
@Test
public void test() {
//获得sqlSession连接
SqlSession sqlSession = MyBatisUtils.getSqlSession();
//使用和指定语句的参数和返回值相匹配的接口
UserDao mapper = sqlSession.getMapper(UserDao.class);
//执行查询方法
List<User> userList = mapper.getAll();
for (User user : userList) {
System.out.println(user.toString());
}
//关闭SqlSession
sqlSession.close();
}
不同作用域和生命周期类别是至关重要的,因为错误的使用会导致非常严重的并发问题。
SqlSessionFactoryBuilder
SqlSessionFactory
SqlSession
#{}
是预编译处理Mybatis 在处理#{}
时,会将 sql 中的#{}替换为?号,创建PreparedStatement
的参数占位符,并通过占位符安全地设置参数(就像使用 ? 一样)
${}
是字符串替换Mybatis 在处理${}
时,就是把${}
替换成变量的值
注意: 使${}
用这种方式接受用户的输入,并用作语句参数是不安全的,会导致潜在的 SQL 注入攻击;使用#{}可以有效的防止 SQL 注入,提高系统安全性
id
– 一个 ID 结果;标记出作为 ID 的结果可以帮助提高整体性能association
– 一个复杂类型的关联;许多结果将包装成这种类型嵌套结果映射 – 关联可以是 resultMap 元素,或是对其它结果映射的引用collection
– 一个复杂类型的集合嵌套结果映射 – 集合可以是 resultMap 元素,或是对其它结果映射的引用@Data
@NoArgsConstructor
public class User {
private int id;
private String username;
private String password;
private Address address;//地址:关联对象
public User(String username, String password) {
this.username = username;
this.password = password;
}
}
关联类
@Data
@NoArgsConstructor
public class Address {
private int userId;
private String country;
private String city;
}
关联对象:
association
关联对象的类型:javaType
<resultMap id="userMap" type="com.yauyukbiu.demo.entity.User">
<id property="id" column="id"/>
<id property="username" column="username"/>
<id property="password" column="password"/>
<association property="address" javaType="com.yauyukbiu.demo.entity.Address">
<id property="userId" column="user_id"/>
<id property="country" column="country"/>
<id property="city" column="city"/>
association>
resultMap>
//获取所有用户信息
<select id="getAll" resultMap="userMap">
select * from user left join address on id=user_id;
select>
@Data
@NoArgsConstructor
public class User {
private int id;
private String username;
private String password;
private List<PhoneNumber> phoneNumber;//多值属性
}
多值属性表
@Data
@NoArgsConstructor
public class PhoneNumber {
private int userId;
private int number;
}
集合:
collection
集合中的对象类型:ofType
<collection property="phoneNumber" ofType="com.yauyukbiu.demo.entity.PhoneNumber">
<id property="userId" column="id"/>
<id property="number" column="number"/>
collection>
<select id="getAll" resultMap="userMap">
select * from user left join phone_number on user.id=phone_number.id
select>
SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING
需要slf4j-api
支持以及slf4j
的日志实现(推荐使用logback
)
思考:为什么要分页?
节约资源优化查询性能,避免一次查询大量数据,增大数据库的压力
语法:
SELECT * FROM table_ LIMIT startIndex, pageSize
startIndex: 索引下标,mysql默认从第0个
开始查询
pageSize: 查询的数据个数
//业务场景:分页查询用户
public interface UserDao {
//分页demo
List<User> getAll(@Param("CurrentPageOn") int CurrentPageOn,
@Param("pageSize") int pageSize);
}
UserMapper.xml:编写对应dao的业务sql语句
<select id="getAll" resultMap="userMap">
select *
from user u
left join address a on u.id = a.user_id
limit #{CurrentPageOn},#{pageSize};
</select>
test
@Test
public void test() {
//获取SqlSession连接
SqlSession sqlSession = MyBatisUtils.getSqlSession();
//使用和指定语句的参数和返回值相匹配的接口
UserDao mapper = sqlSession.getMapper(UserDao.class);
//查询获取全部用户
//分页---------------
int pageOn = 1;//查询页数
int pageSize = 5;//每页查询的数据个数
int CurrentPageOn = ((pageOn) - 1) * pageSize;//当前页数,limit默认从0开始
List<User> userList = mapper.getAll(CurrentPageOn,pageSize);
for (User user : userList) {
System.out.println(user.toString());
}
//关闭SqlSession
sqlSession.close();
}
mybatis提供的SqlSession
接口查询方法selectList
sqlSession.selectList(String var1, Object var2, RowBounds var3);
返回值:list< Object >
sql语句不进行分页,由mybatis执行分页操作
<select id="getAllByRowBounds" resultMap="userMap">
select *
from user u
left join address a on u.id = a.user_id
select>
test
//获取SqlSession连接
SqlSession sqlSession = MyBatisUtils.getSqlSession();
//RowBounds:从第几个开始,获取数据的总数
RowBounds rowBounds = new RowBounds(0, 5);
List<User> userList = sqlSession.selectList("com.yauyukbiu.demo.dao.UserDao.getAllByRowBounds",
null, rowBounds);
for (User user : userList) {
System.out.println(user.toString());
}
//关闭SqlSession
sqlSession.close();
分页插件的原理:实现 Mybatis 提供的接口,实现自定义插件,在插件的拦截方法内拦截待执行的 sql,然后重写 sql。
举例:select * from student,拦截 sql 后重写为:select t.* from (select * from student)tlimit 0,10
PageHelper官方文档:https://pagehelper.github.io/docs/
Mybatis 动态 sql 可以让我们在 xml 映射文件内,以标签的形式编写动态sql,完成逻辑判断和动态拼接sql的功能
使用 OGNL 从 sql 参数对象中计算表达式的值,根据表达式的值动态拼接 sql,以此来完成动态 sql 的功能。
使用动态 SQL 最常见情景是根据条件包含 where
子句的一部分
<select id="findUser" resultType="com.yauyukbiu.demo.entity.User">
SELECT * FROM user
WHERE id=#{id}
<if test="username!=null">
AND username=#{username}
if>
select>
有时候,我们不想使用所有的条件,而只是想从多个条件中选择一个使用。针对这种情况,MyBatis 提供了 choose 元素,它有点像 Java 中的 switch 语句。
<select id="findUserByChoose" resultType="com.yauyukbiu.demo.entity.User">
SELECT * FROM user
<where>
<choose>
<when test="id!=0">
id=#{id}
</when>
<when test="username!=null">
username=#{username}
</when>
<otherwise>
username='令狐冲' //当没有条件匹配时,执行查询username=‘令狐冲’的用户
</otherwise>
</choose>
</where>
</select>
//id=0,username=null:不执行when语句,执行otherwise语句
List<User> userList = userDao.findUserByChoose(0 ,null);
//id=10
List<User> userList = userDao.findUserByChoose(10 ,null);
trim
标签中的 prefix
和 suffix
属性会被⽤于⽣成实际的 SQL 语句,会和标签内部的语句进⾏拼接,如果语句前后出现了 prefixOverrides
或者 suffixOverrides
属性中指定的值,MyBatis 框架会⾃动将其删除。
prefix:前缀
suffix:后缀
select查询语句使用trim动态拼接
<select id="findByAccount" parameterType="com.demo.entity.Account"
resultType="com.southwind.entity.Account">
select * from t_account
<trim prefix="where" prefixOverrides="and">//prefixOverrides若下边拼接的sql语句前缀为and则将其删除
<if test="id!=0">
id = #{id}
if>
<if test="username!=null">
and username = #{username}
if>
<if test="password!=null">
and password = #{password}
if>
<if test="age!=0">
and age = #{age}
if>
trim>
select>
insert插入语句使用trim动态拼接
<insert id="addUser" parameterType="User" useGeneratedKeys="true">
INSERT INTO user
<trim prefix="(" suffix=")" suffixOverrides=",">//suffixOverrides去掉最后一个”,“
<if test="loginName != null">login_name,if>
<if test="email != null">email,if>
<if test="phone != null">phone,if>
<if test="password != null">`password`,if>
<if test="status != null">`status`,if>
<if test="icon != null">icon,if>
<if test="role != null">role,if>
trim>
<trim prefix="VALUES (" suffix=")" suffixOverrides=",">//suffixOverrides去掉最后一个”,“
<if test="loginName != null">#{loginName},if>
<if test="email != null">#{email},if>
<if test="phone != null">#{phone},if>
<if test="password != null">#{password},if>
<if test="status != null">#{status},if>
<if test="icon != null">#{icon},if>
<if test="role != null">#{role},if>
trim>
insert>
where
条件语句and
连接符
where
元素只会在子元素返回任何内容的情况下才插入 “WHERE” 子句。而且,若子句的开头为 “AND” 或 “OR”,where 元素也会将它们去除。
<select id="findUser" resultType="com.yauyukbiu.demo.entity.User">
SELECT * FROM user
<where>
<if test="id!=0">
id=#{id}
if>
<if test="username!=null">
AND username=#{username}
if>
where>
select>
set
标签⽤于 update
操作,会⾃动根据参数选择⽣成 SQL 语句<update id="updateAuthorIfNecessary">
update Author
<set>
<if test="username != null">username=#{username},</if>
<if test="password != null">password=#{password},</if>
<if test="email != null">email=#{email},</if>
<if test="bio != null">bio=#{bio}</if>
</set>
where id=#{id}
</update>
动态 SQL 的另一个常见使用场景是对集合进行遍历(尤其是在构建 IN 条件语句的时候)
foreach
标签可以迭代⽣成⼀系列值,这个标签主要⽤于 SQL 的in
语句
可以将任何可迭代对象(如 List
、Set
等)、Map
对象或者Array
数组对象作为集合参数传递给 foreach。当使用可迭代对象或者数组时,index 是当前迭代的序号,item 的值是本次迭代获取到的元素。当使用 Map 对象(或者 Map.Entry 对象的集合)时,index 是键,item 是值。
普通sql语句
SELECT * FROM `user` WHERE id in(1,5,7,9,11,16)
foreach动态拼接语句
<select id="findUserByIds" parameterType="_int" resultType="com.demo.entity.User">
SELECT * FROM user
<where>
<foreach collection="array" item="id"
open="id in(" separator="," close=")">
#{id}
foreach>
where>
select>
test
List<User> findUserByIds(int[] ids);
//获取sqlSession连接
sqlSession = MyBatisUtils.getSqlSession();
UserDao userDao = sqlSession.getMapper(UserDao.class);
int[] ids = new int[]{5, 6, 8, 12, 15,};
List<User> userList = userDao.findUserByIds(ids);
if (!userList.isEmpty()) {
for (User user : userList) {
System.out.println(user.toString());
}
}
将sql语句中重复的sql片段提取出来放置为公共的,便于复用该sql片段
<sql id="if-id-username">//sql片段id
<if test="id!=0">
id=#{id}
if>
<if test="username!=null">
AND username=#{username}
if>
sql>
调用某一sql片段时,只需添加include
标签即可
refid
为sql片段映射id
<select id="findUserByWhere" resultType="com.demo.entity.User">
SELECT * FROM user
<where>
<include refid="if-id-username"/>
where>
select>
bind
元素允许你在 OGNL 表达式以外创建一个变量,并将其绑定到当前的上下文.
<select id="selectBlogsLike" resultType="Blog">
<bind name="pattern" value="'%' + _parameter.getTitle() + '%'" />
SELECT * FROM BLOG
WHERE title LIKE #{pattern}
select>
1、⼀级缓存:SqlSession 级别,默认开启,并且不能关闭。
操作数据库时需要创建 SqlSession 对象,在对象中有⼀个 HashMap ⽤于存储缓存数据,不同的
SqlSession 之间缓存数据区域是互不影响的。
⼀级缓存的作⽤域是 SqlSession 范围的,当在同⼀个 SqlSession 中执⾏两次相同的 SQL 语句事,第⼀次执⾏完毕会将结果保存到缓存中,第⼆次查询时直接从缓存中获取。
需要注意的是,如果 SqlSession 执⾏了 DML 操作(insert、update、delete),MyBatis 必须将缓存
清空以保证数据的准确性。
2、⼆级缓存:Mapper 级别,默认关闭,可以开启。
使⽤⼆级缓存时,多个 SqlSession 使⽤同⼀个 Mapper 的 SQL 语句操作数据库,得到的数据会存在⼆级缓存区,同样是使⽤ HashMap 进⾏数据存储,相⽐较于⼀级缓存,⼆级缓存的范围更⼤,多个
SqlSession 可以共⽤⼆级缓存,⼆级缓存是跨 SqlSession 的。
⼆级缓存是多个 SqlSession 共享的,其作⽤域是 Mapper 的同⼀个 namespace,不同的 SqlSession两次执⾏相同的 namespace 下的 SQL 语句,参数也相等,则第⼀次执⾏成功之后会将数据保存到⼆级缓存中,第⼆次可直接从⼆级缓存中取出数据
每次创建sqlSession默认开启一级缓存,该缓存仅在当前会话内有效
LRU
, Least Recently Used)算法来清除不需要的缓存。<setting name="cacheEnabled" value="true"/>
<cache
eviction="FIFO"
flushInterval="60000"
size="512"
readOnly="true"/>
这个更高级的配置创建了一个 FIFO 缓存,每隔 60 秒刷新,最多可以存储结果对象或列表的 512 个引用,而且返回的对象被认为是只读的,因此对它们进行修改可能会在不同线程中的调用者产生冲突。
可用的清除策略有:
LRU
– 最近最少使用:移除最长时间不被使用的对象。
FIFO
– 先进先出:按对象进入缓存的顺序来移除它们。
SOFT
– 软引用:基于垃圾回收器状态和软引用规则移除对象。
WEAK
– 弱引用:更积极地基于垃圾收集器状态和弱引用规则移除对象。
默认的清除策略是 LRU
。
flushInterval
(刷新间隔)属性可以被设置为任意的正整数,设置的值应该是一个以毫秒为单位的合理时间量。 默认情况是不设置,也就是没有刷新间隔,缓存仅仅会在调用语句时刷新。
size
(引用数目)属性可以被设置为任意正整数,要注意欲缓存对象的大小和运行环境中可用的内存资源。默认值是 1024。
readOnly
(只读)属性可以被设置为 true 或 false。只读的缓存会给所有调用者返回缓存对象的相同实例。 因此这些对象不能被修改。这就提供了可观的性能提升。而可读写的缓存会(通过序列化)返回缓存对象的拷贝。 速度上会慢一些,但是更安全,因此默认值是 false。
提示: 二级缓存是事务性的。这意味着,当 SqlSession 完成并提交时,或是完成并回滚,但没有执行 flushCache=true 的 insert/delete/update 语句时,缓存会获得更新。
@Data
public class User implements Serializable {
private int id;
private String username;
private String password;
private Address address;
private List<PhoneNumber> phoneNumber;//多值属性
}
<dependency>
<groupId>org.mybatisgroupId>
<artifactId>mybatis-ehcacheartifactId>
<version>1.0.0version>
dependency>
<dependency>
<groupId>net.sf.ehcachegroupId>
<artifactId>ehcacheartifactId>
<version>2.10.5version>
dependency>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd">
<diskStore/>
<defaultCache
eternal="false"
maxElementsInMemory="10000"
maxElementsOnDisk="10000000"
overflowToDisk="false"
timeToIdleSeconds="1800"
timeToLiveSeconds="259200"
memoryStoreEvictionPolicy="LRU">
defaultCache>
ehcache>
<setting name="logImpl" value="SLF4J"/>
<setting name="cacheEnabled" value="true"/>
<cache type="org.mybatis.caches.ehcache.EhcacheCache">
<property name="timeToIdleSeconds" value="3600"/>
<property name="timeToLiveSeconds" value="3600"/>
<property name="memoryStoreEvictionPolicy" value="LRU"/>
cache>
@Data
public class User {
private int id;
private String username;
private String password;
private Address address;
private List<PhoneNumber> phoneNumber;//多值属性
}
延迟加载也叫懒加载、惰性加载,使⽤延迟加载可以提⾼程序的运⾏效率,针对于数据持久层的操作,在某些特定的情况下去访问特定的数据库,在其他情况下可以不访问某些表,从⼀定程度上减少了 Java应⽤与数据库的交互次数
查询学⽣和班级的时,学⽣和班级是两张不同的表,如果当前需求只需要获取学⽣的信息,那么查询学⽣单表即可,如果需要通过学⽣获取对应的班级信息,则必须查询两张表。
不同的业务需求,需要查询不同的表,根据具体的业务需求来动态减少数据表查询的⼯作就是延迟加载
在 config.xml 中开启延迟加载
<settings>
<setting name="logImpl" value="SLF4J"/>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="cacheEnabled" value="true"/>
settings>
将多表关联查询拆分成多个单表查询
实体类User
@Data
public class User {
private int id;
private String username;
private String password;
private Address address;//与Address类关联
}
UserDao
User findUserByLazy(int id);
UserMapper.xml
<resultMap id="userMapLazy" type="com.demo.entity.User">
<id property="id" column="id"/>
<result property="username" column="username"/>
<result property="password" column="password"/>
<association property="address" javaType="com.demo.entity.Address"
select="com.demo.dao.AddressDao.findAddressByLazy" column="id"/>
resultMap>
<select id="findUserByLazy" parameterType="int" resultMap="userMapLazy">
SELECT * FROM user where id=#{id}
select>
关联实体类Address
@Data
@NoArgsConstructor
public class Address {
private int userId;
private String country;
private String city;
}
AddressDao
Address findAddressByLazy(int id);
AddressMapper.xml
<select id="findAddressByLazy" parameterType="int" resultType="com.demo.entity.Address">
select * from address where user_id=#{id}
select>
懒加载测试
1、业务场景一:只需要查询用户的用户名
@Test
public void test() {
//获取sqlSession连接
SqlSession sqlSession = MyBatisUtils.getSqlSession();
UserDao userDao = sqlSession.getMapper(UserDao.class);
User user = userDao.findUserByLazy(5);
System.out.println(user.getUsername());//只需要用户的username
}
结果
从结果看出:由于用户的username在user表中,并不需要关联表的信息;所以只查询了一张表
2、业务场景二:需要查询用户的所有信息,包括关联信息
@Test
public void test() {
SqlSession sqlSession = MyBatisUtils.getSqlSession();
UserDao userDao = sqlSession.getMapper(UserDao.class);
User user = userDao.findUserByLazy(5);
System.out.println(user.toString());//打印用户所有信息
}