Mybatis学习笔记
一、认识Mybatis
-
MyBatis 是一款优秀的持久层框架,
-
它支持自定义 SQL、存储过程以及高级映射。
-
MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。
-
MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。
-
MyBatis本是apache的一个开源项目iBatis,2010年这个项目由apache software foundation迁移到了google code,并且改名为MyBatis。2013年11月迁移到Github.
-
iBATIS一词来源于**“internet”和“abatis”的组合,是一个基于Java的持久层框架**。iBATIS提供的持久层框架包括SQL Maps和Data Access Objects(DAOs)
二,第一个mybatis程序
1、导入对应的jar包
导入mybatis包和数据库驱动包
对应的maven依赖:
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.7</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.25</version>
</dependency>
导入单元测试类junit
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13</version>
<scope>test</scope>
</dependency>
2、编写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>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<!--这里的value填写对应的JDBC四件套
com.mysql.cj.jdbc.Driver是mysql 8.0以上版本,
5.0的为 com.mysql.jdbc.Driver-->
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<!--注意这里的&符号需要用&代替-->
<property name="url" value="jdbc:mysql://localhost:3306/mytestdatabase?useSSL=true&useUnicode=true&character=UTF-8"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
<mappers>
<!--注意这里的mappers标签,后面会用于注册mapper文件-->
</mappers>
</configuration>
3、编写实体类和dao/Mapper接口
实体类(实体类的方法此处省略,使用时需要写上):
注意实体类的属性要和数据库中的字段名一致
package com.li.pojo;
public class User {
private int id;
private String password;
private String name;
}
dao/Mapper接口
package com.li.dao;
import com.li.pojo.User;
import org.apache.ibatis.annotations.Delete;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import java.util.List;
import java.util.Map;
public interface UserMapper {
//User类常用方法
//查询所有用户
public List<User> getUserList();
//通过id查询用户
public User selectUserById(int id);
//通过名字查询用户
public List<User> selectUserByName(Map<String,Object> map);
public int addUser(User user);
public int updateUser(User user);
//如果使用@Param()注解则sql中优先采用注解中的命名,如果参数只有一个基本类型,则可以省略
// 如果有多个参数,则在每个参数前都需加该注解
public int deleteUser(@Param("id") int id);
}
4、编写mapper.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填写对应的映射接口的包路径-->
<mapper namespace="com.li.dao.UserMapper">
<!-- id相当于方法名, resultType为查询结果类型,需要全限定名(包路径)
parameterType为参数类型 基本类型可以省略-->
<select id="getUserList" resultMap="com.li.pojo.User">
select * from t_user
</select>
<select id = "selectUserById" parameterType="int" resultType = "com.li.pojo.User">
<!--使用#{}或${}来获取对应方法中的属性
-->
select * from t_user where id = #{id}
</select>
<select id = "selectUserByName" parameterType="map" resultType = "com.li.pojo.User">
select * from t_user where name = #{name}
</select>
<insert id = "addUser" parameterType= "com.li.pojo.User">
insert into t_user(id,password,name) value (#{id},#{password},#{name})
</insert>
<update id = "updateUser" parameterType = "com.li.pojo.User">
update t_user set name = #{name} ,password = #{password} where id = #{id}
</update>
</mapper>
注意:dao接口中的方法都需要在此处注册,否则无法使用
关于使用#{}和${}的区:
- #{}使用的是预编译的方式执行SQL,可以防止SQL注入
- 而${}是使用普通的类执行SQL,无法防止SQL注入
关于命名空间问题:
**命名解析:**为了减少输入量,MyBatis 对所有具有名称的配置元素(包括语句,结果映射,缓存等)使用了如下的命名解析规则。
- 全限定名(比如 “com.mypackage.MyMapper.selectAllThings)将被直接用于查找及使用。
- 短名称(比如 “selectAllThings”)如果全局唯一也可以作为一个单独的引用。 如果不唯一,有两个或两个以上的相同名称(比如 “com.foo.selectAllThings” 和 “com.bar.selectAllThings”),那么使用时就会产生“短名称不唯一”的错误,这种情况下就必须使用全限定名。
5、在配置文件中注册mapper文件
<mappers>
<mapper class="com.li.dao.UserMapper"/>
</mappers>
6、编写获取SqlSession的工具类
package com.li.util;
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 {
//私有化SqlSessionFactory对象
private static SqlSessionFactory sqlSessionFactory;
static{
//mybatis核心配置文件路径
String resource = "mybatis-config.xml";
InputStream inputStream = null;
try {
//读取文件
inputStream = Resources.getResourceAsStream(resource);
} catch (IOException e) {
e.printStackTrace();
}
//通过mybatis核心配置文件实例化SqlSessionFactory对象
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
}
public static SqlSession getSqlSession(){
//通过SqlSession工厂对象创建Sqlsession对象
//可以在此处配置是否自动提交openSession(boolean autoCommit)
return sqlSessionFactory.openSession();
}
}
7、使用
package com.li.dao;
import com.li.pojo.User;
import com.li.util.MybatisUtil;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class UserMapperTest {
@Test
public void selectTest1(){
//第一步获取SqlSession对象
SqlSession sqlSession = null;
try {
sqlSession = MybatisUtil.getSqlSession();
//执行SQL
//方式一:
//mybatis开发底层原理采用java反射机制
//getMapper()方法默认返回Object对象,参数为待获取的dao接口class对象,不写的话需要强转
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
List<User> users= userMapper.getUserList();
//方式二:(不推荐)
//参数直接定位到方法
//List<User>users = sqlSession.selectList("com.li.dao.UserMapper.getUserList");
for (User user : users) {
System.out.println(user);
}
}finally {
//关闭sqlSession
//建议使用try{}finally{}方式进行关闭
sqlSession.close();
}
}
@Test
public void insertTest(){
SqlSession sqlSession = MybatisUtil.getSqlSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
userMapper.addUser(new User(4,"123123","哈啊哈"));
//注意增删改需要提交
sqlSession.commit();
sqlSession.close();
}
@Test
public void updateTest(){
SqlSession sqlSession = MybatisUtil.getSqlSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
userMapper.updateUser(new User(4,"123321","大肥"));
//注意增删改需要提交
sqlSession.commit();
sqlSession.close();
}
@Test
public void deleteTest(){
SqlSession sqlSession = MybatisUtil.getSqlSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
userMapper.deleteUser(4);
sqlSession.commit();
sqlSession.close();
}
}
三,Map和模糊查询
1、Map作参数
dao接口中的方法:
public List<User> selectUserByName(Map<String,Object> map);
在mapper.xml文件中:
<select id = "selectUserByName" parameterType="map" resultType = "com.li.pojp.User">
<!--当参数类型为map时,可以使用#{}传递key来获取map集合中对应的value值-->
select * from t_user where name = #{name}
</select>
2、模糊查询
dao接口中的方法
public List<User> selectUserByLike(String name);
方式一:在传递值时进行字符串拼接
mapper.xml文件
<select id="selectUserByLike" resultMap="UserMap">
<!--注意是用${}进行取值,不能用#{}-->
select * from t_user where name like '%${name}%'
<!--可以在此处拼接,也可以在传递参数时拼接,无论使用哪种都可以能导致SQL注入-->
</select>
方式二:使用SQL函数concat()进行拼接,该方法可以有效防止SQL注入问题
mapper.xml文件
<select id="selectUserByLike" resultMap="UserMap">
select * from t_user where name like concat('%',#{bookName},'%')
</select>
测试:
@Test
public void selectTest4(){
SqlSession sqlSession = MybatisUtil.getSqlSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
List<User> users = userMapper.selectUserByLike("李");
for (User user : users) {
System.out.println(user);
}
sqlSession.close();
}
四、mybatis配置说明
1、常用配置属性的使用
configuration(配置)
- properties(属性)
- settings(设置)
- typeAliases(类型别名)
- environments(环境配置)
- environment(环境变量)
- transactionManager(事务管理器)
- dataSource(数据源)
- environment(环境变量)
- mappers(映射器)
注意:mybatis配置文件中标签的顺序是指定的依次是:
properties(属性)->settings(设置)->typeAliases(类型别名)->environments(环境配置)->mappers(映射器)
1.properties(属性)
<!--使用resource属性导入外部的数据库配置文件,导入之后可以再配置文件中使用${name}的方式取出所需数据-->
<properties resource="db.properties"/>
<properties>
<!-- 可以在这里自定义属性,类似于java的全局变量,如果与外部资源重名则优先使用外部资源属性-->
<!-- 例如 <property name="username" value="root"/>-->
</properties>
2.environments(环境配置)
<!-- 默认使用development环境,使用别的环境可以再这里使用default属性应用对应的环境id-->
<environments default="development">
<!--可以再这里配置多套环境-->
<!--环境一:-->
<environment id="development">
<!-- 默认使用JDBC管理器,也可以使用MANAGED管理器-->
<transactionManager type="JDBC"/>
<!--数据源配置使用type属性可以应用不同的数据源
常用数据源有dbcp c3p0 druid 数据源的作用都是用于连接数据库,效率和一些功能不同-->
<!-- 数据源默认开启池连接 其他属性有UNPOOLED:不使用池化技术 JNDI:正常连接-->
<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>
<!--环境二-->
<environment id="test">
<!-- 默认使用JDBC管理器-->
<transactionManager type="MANAGED">
<perperty name ="closeConnection" value = "false"/>
</transactionManager>
<!-- 数据源默认开启池连接-->
<dataSource type="UNPOOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
事务管理器说明
3.typeAliases(类型别名)
<!--使用typeAliases标签可以给java类型起别名-->
<typeAliases>
<!-- 方式一:自定义类型-->
<!-- type属性为java类的全限定名,alias属性为给该java类起的别名 -->
<!-- <typeAlias type="com.li.pojo.User" alias="user"/>-->
<!-- 方式二:使用包扫描的方式统一导入自定义类型起别名,别名为类名的首字母小写形式-->
<package name="com.li.pojo"/>
</typeAliases>
起了别名之后就可以在mapper文件中使用了
<!--例如:-->
<select id = "selectUserById" parameterType="int" resultType = "user">
select * from t_user where id = #{id}
</select>
下面是一些为常见的 Java 类型内建的类型别名。它们都是不区分大小写的,注意,为了应对原始类型的命名重复,采取了特殊的命名风格。
别名 | 映射的类型 |
---|---|
_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 | Iterator |
4.settings(设置)
这是 MyBatis 中极为重要的调整设置,它们会改变 MyBatis 的运行时行为。 它的属性中包括设置中各项设置的含义、默认值等。
而我们常用的有
设置名 | 描述 | 有效值 | 默认值 |
---|---|---|---|
cacheEnabled | 全局性地开启或关闭所有映射器配置文件中已配置的任何缓存。 | true | false | true |
autoMappingBehavior | 指定 MyBatis 应如何自动映射列到字段或属性。 NONE 表示关闭自动映射;PARTIAL 只会自动映射没有定义嵌套结果映射的字段。 FULL 会自动映射任何复杂的结果集(无论是否嵌套)。 | NONE, PARTIAL, FULL | PARTIAL |
safeRowBoundsEnabled | 是否允许在嵌套语句中使用分页(RowBounds)。如果允许使用则设置为 false。 | true | false | False |
mapUnderscoreToCamelCase | 是否开启驼峰命名自动映射,即从经典数据库列名 A_COLUMN 映射到经典 Java 属性名 aColumn。 | true | false | False |
localCacheScope | MyBatis 利用本地缓存机制(Local Cache)防止循环引用和加速重复的嵌套查询。 默认值为 SESSION,会缓存一个会话中执行的所有查询。 若设置值为 STATEMENT,本地缓存将仅用于执行语句,对相同 SqlSession 的不同查询将不会进行缓存。 | SESSION | STATEMENT | SESSION |
returnInstanceForEmptyRow | 当返回行的所有列都是空时,MyBatis默认返回 null 。 当开启这个设置时,MyBatis会返回一个空实例。 请注意,它也适用于嵌套的结果集(如集合或关联)。(新增于 3.4.2) | true | false | false |
logPrefix | 指定 MyBatis 增加到日志名称的前缀。 | 任何字符串 | 未设置 |
logImpl | 指定 MyBatis 所用日志的具体实现,未指定时将自动查找。 | SLF4J | LOG4J(deprecated since 3.5.9) | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING | 未设置 |
即使可选设置如此之多,但我们往往只使用其中的个别属性例如
<settings>
<!-- 开启驼峰命名映射-->
<setting name="mapUnderscoreToCamelCase" value="true"/>
<!-- 设置使用JDK标准的日志工厂:-->
<setting name="logImpl" value="STDOUT_LOGGING"/>
<!--显示的声明使用缓存-->
<setting name="cacheEnable" value="true"/>
</settings>
5.mappers(映射器)
在mybatis配置文件中mappers标签用于注册dao层的mapper接口
<mappers>
<!-- 方式一:使用资源引用的方式-->
<mapper resource="com/li/dao/StudentMapper.xml"/>
<!-- 方式二:使用接口名, Mapper.xml文件和绑定的接口必须放在一个包下,且同名-->
<mapper class="com.li.dao.UserMapper"/>
<!-- 方式三:包扫描,Mapper.xml文件和绑定的接口必须放在一个包下,且同名-->
<!-- <package name="com.li.dao"/>-->
</mappers>
2、生命周期和作用域
生命周期和作用域是十分重要的,因为错误会导致十分严重的并发问题
SqlSessionFactoryBuilder
- 只需要创建一次,用于构建SqlSessionFactory,
- 一旦构建成功就不再需要它了
- 最佳作用域为局部变量
SqlSessionFactory
- 一旦创建就一致存在
- 类似于JDBC的数据库连接池
- 最佳作用域为全局作用域,例如使用单例模式或者静态单例模式
SqlSession
- 连接到连接池的请求,用于执行SQL
- 每个线程都有自己的SqlSession,不是线程安全的
- 用完需要关闭,避免资源的浪费
- 最佳作用域是请求或者方法作用域中
五、ResultMap结果集映射
1、字段名不一致问题
当数据库的字段名和java实体类对象的属性名不一致时,我们就需要在mapper文件中使用返回结果集映射。
方式一:使用结果集映射
<!--resultMap相当于结果集映射,当字段和java实体类的属性名不一致时,
需要在此处做配置映射,一致的字段则可以省略-->
<resultMap id="userMap" type="User">
<!--id标签对应为该表中的id,可以直接使用result标签-->
<!--标签中的property值对应java实体类中的字段,标签中的column值对应数据库中相应的字段,如果使用了别名,则写别名-->
<id property="id" column="id"/>
<result property="name" column="name"/>
<result property="password" column="password"/>
</resultMap>
<!-- id相当于方法名, resultType为查询结果类型,需要些全限定位(包路径) parameterType为参数类型-->
<select id="getUserList" resultMap="userMap">
select * from t_user
</select>
方式二:使用SQL起别名
例如select id as tid from t_user
把不一致的字段用as起别名为一致即可
Id 和 Result 的属性
属性 | 描述 |
---|---|
property | 映射到列结果的字段或属性。如果 JavaBean 有这个名字的属性(property),会先使用该属性。否则 MyBatis 将会寻找给定名称的字段(field)。 无论是哪一种情形,你都可以使用常见的点式分隔形式进行复杂属性导航。 比如,你可以这样映射一些简单的东西:“username”,或者映射到一些复杂的东西上:“address.street.number”。 |
column | 数据库中的列名,或者是列的别名。一般情况下,这和传递给 resultSet.getString(columnName) 方法的参数一样。 |
javaType | 一个 Java 类的全限定名,或一个类型别名(关于内置的类型别名,可以参考上面的表格)。 如果你映射到一个 JavaBean,MyBatis 通常可以推断类型。然而,如果你映射到的是 HashMap,那么你应该明确地指定 javaType 来保证行为与期望的相一致。 |
2、多对一问题
实体类
package com.li.pojo;
public class Teacher {
private int id;
private String name;
}
//多个学生都有一个共同的学生
public class Student {
private int id;
private String name;
private Teacher teacher;
}
dao层的接口
package com.li.dao;
import com.li.pojo.Student;
import com.li.pojo.Teacher;
import java.util.List;
public interface StudentMapper {
public List<Student> getStudentList();
public List<Student> getStudentList2();
public Teacher getTeacher(int id);
}
XML文件
<mapper namespace="com.li.dao.StudentMapper">
<!--多对一方式一 :按结果嵌套处理 优点结果嵌套利于理解,但是SQL复杂,即把所有需要的字段一次性都查出来(推荐) -->
<resultMap id="StudentMapper" type="student">
<result property="id" column="sid"/>
<result property="name" column="sname"/>
<!-- 学生的第三个属性为Teacher对象 使用association标签进行映射
如果是集合就用Collection标签进行映射
-->
<association property="teacher" javaType="teacher">
<!-- 老师的属性映射-->
<result property="id" column="tid"/>
<result property="name" column="tname"/>
</association>
</resultMap>
<!-- 注意,对象所需要的的属性,都要查出来-->
<select id="getStudentList" resultMap="StudentMapper">
select s.id sid ,s.name sname ,t.name tname,t.id tid
from t_student s join t_teacher t
where s.tid = t.id;
</select>
<!-- 方式二:采用子表查询的方式 优点SQL语句简单,但是映射复杂 -->
<resultMap id="StudentMapper2" type="student">
<result property="id" column="id"/>
<result property="name" column="name"/>
<!-- 这种方式是先把学生中的信息查出来,再查询老师表,绕后进行信息匹配,效率比较低-->
<!--
<association property="teacher" column="tid" javaType="teacher" select="getTeacher"/>
</resultMap>
<!-- 先执行-->
<select id="getStudentList2" resultMap="StudentMapper2">
select * from mytestdatabase.t_student
</select>
<!-- 后执行-->
<select id="getTeacher" resultType="teacher" parameterType="_int">
select * from t_teacher where id = #{tid}
</select>
</mapper>
3、一对多问题
实体类:
package com.li.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
@AllArgsConstructor
@NoArgsConstructor
@Data
public class Teacher2 {
private int id;
private String name;
//老师中有一组学生
private List<Student2> student2s;
}
@AllArgsConstructor
@NoArgsConstructor
@Data
public class Student2 {
private int id ;
private String name;
//学生有老师的唯一id
private int tid;
}
dao接口:
package com.li.dao;
import com.li.pojo.Student2;
import com.li.pojo.Teacher2;
import org.apache.ibatis.annotations.Param;
public interface Teacher2Mapper {
//@Param注解可以指定接口的参数传递到对应mapper文件时的名称
public Student2 getStudent2sByTid(@Param("tid")int id);
public Teacher2 getTeacherByTid(@Param("tid") int id);
public Teacher2 getTeacherByTid2(@Param("tid") int id);
}
mapper映射:
<?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填写对应的映射接口的包路径-->
<mapper namespace="com.li.dao.Teacher2Mapper">
<!-- 一对多处理方式,方式一:结果集嵌套-->
<resultMap id="Teacher2Mapper" type="Teacher2">
<id property="id" column="tid"/>
<result property="name" column="tname"/>
<collection property="student2s" ofType="student2">
<id property="id" column="sid"/>
<result property="name" column="sname"/>
<result property="tid" column="tid"/>
</collection>
</resultMap>
<select id="getTeacherByTid" parameterType="_int" resultMap="Teacher2Mapper">
select s.id sid ,s.name sname,s.tid stid ,t.id tid,t.name tname
from mytestdatabase.t_teacher t join mytestdatabase.t_student s on t.id = s.tid
where t.id = #{tid}
</select>
<!-- 一对多方式二:-->
<resultMap id="Teacher2Mapper2" type="teacher2">
<result property="id" column="tid"/>
<result property="name" column="tname"/>
<!-- 而该标签中的 column是查询sql中参数元素,是查询返回的java集合类型,ofType是java集合中的元素类型-->
<collection property="student2s" javaType="ArrayList" ofType="student2" select="getStudent2sByTid" column="tid"/>
</resultMap>
<resultMap id="Student2Mapper2" type="student2">
<id property="id" column="sid"/>
<result property="name" column="sname"/>
<result property="tid" column="tid"/>
</resultMap>
//先执行
<select id="getTeacherByTid2" parameterType="_int" resultMap="Teacher2Mapper2">
select t.id tid, t.name tname from mytestdatabase.t_teacher t where t.id = #{tid}
</select>
//后执行
<select id="getStudent2sByTid" resultMap = "Student2Mapper2">
select s.name sname,s.id sid,s.tid tid from mytestdatabase.t_student s where tid = #{tid}
</select>
</mapper>
4.其它问题:
1.关联的嵌套 Select 查询
属性 | 描述 |
---|---|
column | 数据库中的列名,或者是列的别名。一般情况下,这和传递给 resultSet.getString(columnName) 方法的参数一样。 注意:在使用复合主键的时候,你可以使用 column="{prop1=col1,prop2=col2}" 这样的语法来指定多个传递给嵌套 Select 查询语句的列名。这会使得 prop1 和 prop2 作为参数对象,被设置为对应嵌套 Select 语句的参数。 |
select | 用于加载复杂类型属性的映射语句的 ID,它会从 column 属性指定的列中检索数据,作为参数传递给目标 select 语句。 具体请参考下面的例子。注意:在使用复合主键的时候,你可以使用 column="{prop1=col1,prop2=col2}" 这样的语法来指定多个传递给嵌套 Select 查询语句的列名。这会使得 prop1 和 prop2 作为参数对象,被设置为对应嵌套 Select 语句的参数。 |
fetchType | 可选的。有效值为 lazy 和 eager 。 指定属性后,将在映射中忽略全局配置参数 lazyLoadingEnabled ,使用属性的值。 |
六、日志
1、日志的设置
日志的使用,需要在mybatis配置文件中显式的开启,未指定时将自动查找。
<settings>
<!--注意不要有空格-->
<setting name="logImpl" value="STDOUT_LOGGING"/>
2、常用的日志有
- 1.SLF4J
- 2.LOG4J(deprecated since 3.5.9) 【掌握】
- 3.LOG4J2
- 4.JDK_LOGGING
- 5.COMMONS_LOGGING
- 6.STDOUT_LOGGING【掌握】
- 7.NO_LOGGING【不开启日志】
STDOUT_LOGGING标准日志只需要显式的开启即可,无需导包
3、LOG4J日志
1.什么是LOG4J
-
Log4j是Apache的一个开源项目,
-
通过使用Log4j,我们可以控制日志信息输送的目的地是控制台、文件、GUI组件,甚至是套接口服务器、NT的事件记录器、UNIX Syslog[守护进程等;
-
我们也可以控制每一条日志的输出格式;
-
通过定义每一条日志信息的级别,我们能够更加细致地控制日志的生成过程。
-
这些可以通过一个配置文件来灵活地进行配置,而不需要修改应用的代码。
2.LOG4J的使用
1.首先导入对应的jar包
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
2.配置log4j配置文件
#将等级为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/li.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
3.在mybatis配置文件中声明
<settings>
<!--注意不要有空格-->
<setting name="logImpl" value="LOG4J"/>
配置完以上操作后mybatis就可以自动使用了
当然log4j的功能还不止于此,log4j的自定义使用
package com.li.dao;
import com.li.pojo.Teacher2;
import com.li.util.MybatisUtil;
import org.apache.ibatis.session.SqlSession;
import org.apache.log4j.Logger;
import org.junit.Test;
public class Teacher2Test {
//1.定义一个全局静态变量
//注意不要倒导错包 org.apache.log4j.Logger;
static Logger logger = Logger.getLogger(Teacher2Test.class);
@Test
public void selectTest(){
//2.使用
//输出自己想要的信息
logger.info("[info]进入了selectTest()方法");
logger.debug("[debug]进入了selectTest()方法");
logger.error("[error]进入了selectTest()方法");
SqlSession sqlSession = MybatisUtil.getSqlSession();
Teacher2Mapper teacher2Mapper = sqlSession.getMapper(Teacher2Mapper.class);
Teacher2 teacher2 = teacher2Mapper.getTeacherByTid(2);
System.out.println(teacher2);
sqlSession.close();
}
}
七、分页问题
1、SQL分页
SQL语法:
select * from t_user limit startIndex,pageSize;
seletc * from t_user limit pageSize;
从startIndex开始查询,每页显示pageSize个,若startIndex省略,则默认从0开始查询
查询第n页数据:
select * from t_user limit (n-1)*pageSize,pageSize;
2、RowBounds分页
dao接口方法
public List<User> getUserByRowBounds();
mapper
<select id="getUserByRowBounds" resultMap="UserMap">
select * from t_user;
</select>
java代码
public void rowBoundsTest(){
SqlSession sqlSession = MybatisUtil.getSqlSession();
//通过Java代码层面实现分页
//构造方法传递起始页和每页大小
RowBounds rowBounds = new RowBounds(1,2);
//注意selectList()方法第三个参数传递RowBounds对象,第二个参数不使用为空
List<User> lists = sqlSession.selectList("com.li.dao.UserMapper.getUserByRowBounds",null,rowBounds);
for (int i = 0; i < lists.size(); i++) {
System.out.println(lists.get(i));
}
sqlSession.close();
}
八、mybatis注解开发
1、面向接口编程的好处
- 解耦,可拓展,提高复用
- 分层开发中,上层不用管具体的实现
- 都遵守共同的标准,使得开发变得更容易,规范性更好
三个面向区别:
- 面向对象是指,我们考虑问题时,以对象为单位,考虑它的属性及方法
- 面向过程是指,我们考虑问题时,以一个具体的流程(事务过程)为单位,考虑它的实现。
- 接口设计与非接口设计是针对复用技术而言的,与面向对象(过程)不是一个问题,更多的体现就是对系统整体的架构
2、使用注解开发
在dao层接口中使用注解,例如:
@Select("select * from t_user where name like '%${name}%'")
public List<User> selectUserByLike(String name);
public int addUser(User user);
public int updateUser(User user);
//如果使用@Param()注解则sql中优先采用注解中的命名,但是不适合复杂类型
// 如果参数只有一个基本类型,则可以省略,如果有多个参数,则在每个参数前都需加该注解
@Insert("insert into t_user(id,password,name) value (#{id},#{password},#{name})")
public int addUser(User user);
@Update("update t_user set name = #{name} ,password = #{password} where id = #{id}")
public int updateUser(User user);
//如果使用@Param()注解则sql中优先采用注解中的命名,如果参数只有一个基本类型,则可以省略
// 如果有多个参数,则在每个参数前都需加该注解
@Delete("delete from t_user where id = #{id}")
public int deleteUser(@Param("id") int id);
@Delete("delete from t_user where id = #{id}")
public int deleteUser(@Param("id") int id);
注意事项:
- 使用注解的方法不能再在对应的Mapper.xml中配置SQL语句,否则会报错,全部使用注解,可以不再配置mapper文件
- 使用注解开发任然需要在mybatis配置文件中注册对应的接口
- 注解不适合复杂的sql语句
mybatis配置文件中注册dao接口
<mappers>
<mapper class="xiaoqi.dao.UserMapper"></mapper>
</mappers>
mybatis注解开发原理:
-
底层实现机制:反射
-
底层:动态代理
九、mybatis执行流程
1.什么是SqlSession
-
SqlSession是MyBatis的关键对象,
-
是执行持久化操作的独享,类似于JDBC中的Connection.
-
它是应用程序与持久层之间执行交互操作的**一个单线程对象,**也是MyBatis执行持久化操作的关键对象.
-
SqlSession对象完全包含以数据库为背景的所有执行SQL操作的方法,它的底层封装了JDBC连接【重点】
-
可以用SqlSession实例来直接执行被映射的SQL语句
-
每个线程都应该有它自己的SqlSession实例.**
-
SqlSession的实例不能被共享,同时SqlSession也是线程不安全的
-
绝对不能讲SqlSeesion实例的引用放在一个类的静态字段甚至是实例字段中.也绝不能将SqlSession实例的引用放在任何类型的管理范围中,比如Servlet当中的HttpSession对象中.使用完SqlSeesion之后关闭Session很重要,应该确保使用finally块来关闭它.
2.SqlSession的创建流程
-
1.SqlSessionFactoryBuilder—>SqlSessionFactory—>SqlSession
-
2.SqlSessionFactoryBuilder只需要创建一次,建议使用局部对象的方式创建
-
3.SqlSessionFactoryBuilder 则可以从 XML 配置文件[mybatis核心配置文件]或一个**预先配置的 Configuration 实例****来构建出 SqlSessionFactory 实例。
-
4.既然有了 SqlSessionFactory,我们可以从中获得 SqlSession 的实例。
mybatis执行流程,
十、动态SQL
1、,when和if标签
<!-- where 标签只会在子元素返回任何内容(可以没有if但是存在内容)的情况下才插入 “WHERE” 子句。
而且,若子句的开头为 “AND” 或 “OR”,where 标签也会将它们去除-->
<!-- if标签中的test的值为一个条件表达式,当值为真时则添加if标签中的内容
例如:"title != null",意为从map集合中取出key为title的value,并做该值不为空的判断
-->
<select id="selectBlogWhere" parameterType="map" resultType="blog">
select * from mytestdatabase.t_blog
<where>
<if test="title != null">
title = #{title}
</if>
<if test="author != null">
and author = #{author}
</if>
</where>
</select>
2、choose标签
<!-- 有时候,我们不想使用所有的条件,而只是想从多个条件中选择一个使用。
针对这种情况,MyBatis 提供了 choose 元素,
它有点像 Java 中的 switch 语句。依次寻找when中test属性为真的标签,
如果某个when标签为真,则添加该标签中内容,都不符合则添加otherwise标签中的内容
-->
<select id="selectBlogChoose" parameterType="map" resultType="blog">
select * from mytestdatabase.t_blog
<where>
<choose>
<when test="title != null">
title = #{title}
</when>
<when test="author != null">
and author = #{author}
</when>
<when test="views != null">
and views = #{views}
</when>
<otherwise>
and views >1000
</otherwise>
</choose>
</where>
</select>
3、set标签
<!-- set 元素会动态地在行首插入 SET 关键字,
并会删掉额外的逗号(这些逗号是在使用条件语句给列赋值时引入的)
set标签中test为真的if标签中的内容都会被添加
。-->
<update id="updateBlog" parameterType="map">
update t_blog
<set>
<if test="title != null">
title = #{title},
</if>
<if test="author != null">
author = #{author},
</if>
<if test="views != null">
views = #{views}
</if>
</set>
<where>
id = #{id}
</where>
</update>
4、foreach标签
<!--foreach和java中的增强for类似,从collection属性指向的集合中,用item属性指向的值遍历
如果集合中有元素,则以open属性指向的字符串作为遍历的‘头’,以separator属性指向的值为分隔符,
以close属性指向的值为字符串的结尾
-->
<select id="selectForeach" parameterType="map" resultType="blog">
select * from t_blog
<where>
<foreach collection="ids" item="id" open="and (" separator="or" close=")">
id = #{id}
</foreach>
</where>
</select>
5、sql片段
<!--在mapper文件中可以使用SQL标签来定义一个SQL片段,
并且可以在该文件的其他地方使用include便签来引用该片段,
以提高代码的利用率
-->
<sql id="title-author-views-if">
<if test="title != null">
and title = #{title}
</if>
<if test="author != null">
and author = #{author}
</if>
<if test="views != null">
and author = #{views}
</if>
</sql>
<select id="selectBlogIf" parameterType="map" resultType="blog">
select * from mytestdatabase.t_blog
<where>
<include refid="title-author-views-if"></include>
</where>
</select>
十一、缓存问题
1、缓存简介
当需要频繁查询数据库时,就需要频繁地连接数据,这是十分耗费耗资源的!
如果我们将一次查询的结果,给他暂存在一个可以取到的地方! 例如内存中,我们就可以将其缓存起来,当我们再次查询相同数据的时候,直接走缓存,就不用走数据库了。
1.什么是缓存[Cache]?
存在内中的临时数据
将用户经常查询的数据放在缓存(内存)中,用户去查询数据就不用从磁盘上(关系型数据库数据文件)查询,从缓存中查询,从而提高查询效率,解决了高并发系统的性能问题
2.为什么使用缓存?
减少和数据库的交互次数,减少系统开销,提高系统效率
3.什么样的数据能使用缓存?
经常查询并且不经常改变的数据【可以使用缓存】
Mybatis中的缓存:
Mybatis包含一个非常强大的查询缓存特性,它可以非常方便的定制和配置缓存。缓存可以极大的提升查询效率。
Mybatis系统默认定义了两级缓存:一级缓存和二级缓存
默认情况下,只有一级缓存开启。(SqlSession级别的缓存,也称本科缓存)
二级缓存需要手动开启和配置,它是基于namespace级别的缓存
为了提高扩展性,Mybatis定义了缓存接口Cache。我们可以通过实现Cache接口来自定义二级缓存
2、一级缓存
一级缓存也叫本地缓存:SqlSession
- 与数据库同一次会话期间查询到的数据会放在本地缓存中
- 以后如果需要获取相同的数据,直接从缓存中拿,没有必要再去查询数据库
缓存测试(需要先开启日志):
查询语句:
@Test
public void selectTest2(){
SqlSession sqlSession = MybatisUtil.getSqlSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
User user = userMapper.selectUserById(1);
System.out.println(user);
sqlSession.close();
}
结果:
如果当我们连续多次查询相同的结果时
@Test
public void selectTest2(){
SqlSession sqlSession = MybatisUtil.getSqlSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
User user = userMapper.selectUserById(1);
User user1 = userMapper.selectUserById(1);
User user2 = userMapper.selectUserById(1);
System.out.println(user);
System.out.println(user1);
System.out.println(user2);
sqlSession.close();
}
结果:只查询了一次,很明显第二,三次查询走的缓存
这是一次SqlSession的缓存即:一级缓存
一级缓存也有失效的时候,例如:
1.查询不同的东西
@Test
public void selectTest2(){
SqlSession sqlSession = MybatisUtil.getSqlSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
User user = userMapper.selectUserById(1);
User user1 = userMapper.selectUserById(2);
System.out.println(user);
System.out.println(user1);
sqlSession.close();
}
结果:查询了两次
2.查询过程中有增删改操作,这必然会更新缓存
@Test
public void selectTest2(){
SqlSession sqlSession = MybatisUtil.getSqlSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
User user = userMapper.selectUserById(1);
userMapper.deleteUser(4);
User user1 = userMapper.selectUserById(1);
System.out.println(user);
System.out.println(user1);
sqlSession.close();
}
结果:很明显有了删除的操作后,相同的内容查询了两次
3.查询不同的Mapper
不同的mapper内容很明显不同
4.手动清理缓存
@Test
public void selectTest2(){
SqlSession sqlSession = MybatisUtil.getSqlSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
User user = userMapper.selectUserById(1);
//手动刷新缓存
sqlSession.clearCache();
User user1 = userMapper.selectUserById(1);
System.out.println(user);
System.out.println(user1);
sqlSession.close();
}
结果:查询两次
小结:一级缓存默认是开启的,只在一次SqlSession中有效,也就是拿到连接到关闭连接这个阶段有效
一级缓存就是一个map
3、二级缓存
- 二级缓存也叫全局缓存,一级缓存作用于太低,所以诞生了二级缓存;
- 基于namespace级别的缓存,一个名称空间,对应一个二级缓存;
工作机制; - 一个会话查询一条数据,这个数据就会被放在当前会话的一级缓存中;
如果当前会话关闭了,这个会话对应的一级缓存就没了;但是我们想要的是,会话关闭了,一级缓存中的数据被保存到二级缓存中;新的会话查询信息,就可以从二级缓存中获取内容 - 不同的mapper查出的数据会放在自己对应的缓存(map)中
二级缓存的使用步骤
1.在mybatis配置文件中的settings标签中显示的开启缓存(其实默认是开启状态,只需要在mapper文件中配置缓存即可)
<setting name="cacheEnabled" value="true"/>
cacheEnabled | 全局性地开启或关闭所有映射器配置文件中已配置的任何缓存。 | true | false | true |
---|
2.在mapper文件中开启缓存
最简单的开启方式
<cache/>
配置缓存
<cache
eviction="FIFO"
flushInterval="60000"
size="512"
readOnly="true"/>
<!--
这个更高级的配置创建了一个 FIFO【先进先出】 缓存,
每隔 60 秒刷新,
最多可以存储结果对象或列表的 512 个引用,
而且返回的对象被认为是只读的,
因此对它们进行修改可能会在不同线程中的调用者产生冲突。
eviction清除策略有:LRU[默认]【清除使用最少】,FIFO【先进先出】,SOFT【软引用】,WEAK【弱引用】
-->
测试:
@Test
public void selectTest2(){
SqlSession sqlSession = MybatisUtil.getSqlSession();
SqlSession sqlSession1 = MybatisUtil.getSqlSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
User user = userMapper.selectUserById(1);
System.out.println(user);
sqlSession.close();
//关掉第一个sqlSession才会将一级缓存中的数据保存到二级缓存中
UserMapper userMapper1 = sqlSession1.getMapper(UserMapper.class);
User user1 = userMapper1.selectUserById(1);
System.out.println(user);
System.out.println(user1==user);
sqlSession1.close();
}
结果:只查询一次数据库,且关闭的连接再次被返回
小结:
-
只要开启了二级缓存,只在同一个Mapper下有效
-
所有的数据都会先放在一级缓存中;只有当会话提交或者关闭的时候,才会提交到二级缓存中
缓存原理
4、自定义缓存–ehcache
Ehcache是一种广泛使用的开源Java分布式缓存,主要面向通用缓存
要在程序中使用ehcache:
1.首先需要导入ehcache的包,注意是mybatis-ehcache
<!-- https://mvnrepository.com/artifact/org.mybatis.caches/mybatis-ehcache -->
<dependency>
<groupId>org.mybatis.caches</groupId>
<artifactId>mybatis-ehcache</artifactId>
<version>1.2.1</version>
</dependency>
2.编写ehcache配置文件
<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd" updateCheck="false">
<!--
diskStore:为缓存路径,ehcache分为内存和磁盘两级,此属性定义磁盘的缓存位置。参数解释如下:
user.home – 用户主目录
user.dir – 用户当前工作目录
java.io.tmpdir – 默认临时文件路径
-->
<diskStore path="./tmpdir/Tmp_EhCache"/>
<defaultCache
eternal="false"
maxElementsInMemory="10000"
overflowToDisk="false"
diskPersistent="false"
timeToIdleSeconds="1800"
timeToLiveSeconds="259200"
memoryStoreEvictionPolicy="LRU"/>
<cache
name="cloud_user"
eternal="false"
maxElementsInMemory="5000"
overflowToDisk="false"
diskPersistent="false"
timeToIdleSeconds="1800"
timeToLiveSeconds="1800"
memoryStoreEvictionPolicy="LRU"/>
<!--
defaultCache:默认缓存策略,当ehcache找不到定义的缓存时,则使用这个缓存策略。只能定义一个。
-->
<!--
name:缓存名称。
maxElementsInMemory:缓存最大数目
maxElementsOnDisk:硬盘最大缓存个数。
eternal:对象是否永久有效,一但设置了,timeout将不起作用。
overflowToDisk:是否保存到磁盘,当系统当机时
timeToIdleSeconds:设置对象在失效前的允许闲置时间(单位:秒)。仅当eternal=false对象不是永久有效时使用,可选属性,默认值是0,也就是可闲置时间无穷大。
timeToLiveSeconds:设置对象在失效前允许存活时间(单位:秒)。最大时间介于创建时间和失效时间之间。仅当eternal=false对象不是永久有效时使用,默认是0.,也就是对象存活时间无穷大。
diskPersistent:是否缓存虚拟机重启期数据 Whether the disk store persists between restarts of the Virtual Machine. The default value is false.
diskSpoolBufferSizeMB:这个参数设置DiskStore(磁盘缓存)的缓存区大小。默认是30MB。每个Cache都应该有自己的一个缓冲区。
diskExpiryThreadIntervalSeconds:磁盘失效线程运行时间间隔,默认是120秒。
memoryStoreEvictionPolicy:当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存。默认策略是LRU(最近最少使用)。你可以设置为FIFO(先进先出)或是LFU(较少使用)。
clearOnFlush:内存数量最大时是否清除。
memoryStoreEvictionPolicy:可选策略有:LRU(最近最少使用,默认策略)、FIFO(先进先出)、LFU(最少访问次数)。
FIFO,first in first out,这个是大家最熟的,先进先出。
LFU, Less Frequently Used,就是上面例子中使用的策略,直白一点就是讲一直以来最少被使用的。如上面所讲,缓存的元素有一个hit属性,hit值最小的将会被清出缓存。
LRU,Least Recently Used,最近最少使用的,缓存的元素有一个时间戳,当缓存容量满了,而又需要腾出地方来缓存新的元素的时候,那么现有缓存元素中时间戳离当前时间最远的元素将被清出缓存。
-->
</ehcache>
3.在mapper文件中配置
<cache type = "org.mybatis.caches.ehcache.EhcacheCache" />
4.使用即可
创作不易,点赞支持一下谢谢