目录
1、简介
2、入门
3、核心文件
4、映射文件
5、动态SQL
6、注解SQL
7、缓存
8、拓展
1、简介
1.1、什么是Mybatis?
- MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。
- MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象,即实体类)为数据库中的记录。
- Mybatis是一个半ORM(对象关系映射)框架,它内部封装了JDBC,开发时只需要关注SQL语句本身,不需要花费精力去处理加载驱动、创建连接、创建statement等繁杂的过程。程序员直接编写原生态sql,可以严格控制sql执行性能,灵活度高。
- MyBatis 可以使用 XML 或注解来配置和映射原生信息,将 POJO映射成数据库中的记录,避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。
- 通过xml 文件或注解的方式将要执行的各种 statement 配置起来,并通过java对象和 statement中sql的动态参数进行映射生成最终执行的sql语句,最后由mybatis框架执行sql并将结果映射为java对象并返回。(从执行sql到返回result的过程)。
1.2、历史
MyBatis框架的前身是iBatis,是Apache软件基金会的一个开源项目。2010年,这个项目有Apache Software Foundation迁移到了Google Code,并更名位MyBatis,2013年,项目迁移到了GitHub上。
1.3、Mybaits的优点
- 基于SQL语句编程,相当灵活,不会对应用程序或者数据库的现有设计造成任何影响,SQL写在XML里,解除sql与程序代码的耦合,便于统一管理;提供XML标签,支持编写动态SQL语句,并可重用。
- 与JDBC相比,减少了50%以上的代码量,消除了JDBC大量冗余的代码,不需要手动开关连接;
- 很好的与各种数据库兼容(因为MyBatis使用JDBC来连接数据库,所以只要JDBC支持的数据库MyBatis都支持)。
- 能够与Spring很好的集成;
- 提供映射标签,支持对象与数据库的ORM字段关系映射;提供对象关系映射标签,支持对象关系组件维护。
1.4、MyBatis框架的缺点
- SQL语句的编写工作量较大,尤其当字段多、关联表多时,对开发人员编写SQL语句的功底有一定要求。
- SQL语句依赖于数据库,导致数据库移植性差,不能随意更换数据库。
- 不支持方法重载!
1.5、MyBatis框架适用场合
- MyBatis专注于SQL本身,是一个足够灵活的DAO层解决方案。
- 对性能的要求很高,或者需求变化较多的项目,如互联网项目,MyBatis将是不错的选择。
2、入门
如果你只是想要进行简单操作,那么按照以下步骤来,即可掌握mybatis的基本用法!
- 导入依赖
- 编写核心配置文件
- 编写工具类
- 编写pojo类
- 编写pojo类的接口
- 编写映射文件
- 测试
2.1导入依赖
要使用 MyBatis, 只需将 mybatis-x.x.x.jar 文件置于类路径(classpath)中即可。
如果使用 Maven 来构建项目,则需将下面的依赖代码置于 pom.xml 文件中:
<!--这里用的是3.4.6版本,其他版本请到maven仓库自行查找-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.6</version>
</dependency>
2.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> <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="org/mybatis/example/BlogMapper.xml"/> </mappers> </configuration>
-
在核心配置文件中,将四个property标签的value值替换成对应驱动、地址、账号、密码即可,这一步操作可以连接上数据库!
-
mappers标签则是注册编写的接口对应的mapper.xml文件!
2.3编写工具类
-
你需要编写一个工具类(MybatisUtils)用来获取连接
public class MybatisUtils { private static SqlSessionFactory sqlSessionFactory; static { try { String resource="mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); } catch (IOException e) { e.printStackTrace(); } } public static SqlSession getSqlSession(){ //你可以在这里给openSession()传一个参数true,这样就无需手动提交了! return sqlSessionFactory.openSession(); } }
-
这个工具类可以方便的获取到数据库的连接,并且充分的利用到了一些属性的作用域,使其不会占用大量资源,让系统性能降低!
-
其中==String resource=“mybatis-config.xml”;==这里的"mybatis-config.xml"对应的是你的核心配置文件,如果你的核心配置文件并不是这个名称,请写你自己的核心配置文件名称!
-
getSqlSession()方法则是获取数据库的连接,它是一个静态的方法,所以可以使用类名去调用!
2.4编写pojo类
- 请按照你的数据库,去编写你的实体类!
2.5编写pojo类的接口
请按照你的实体类,去编写你的接口。如:
public interface StudentMapper {
int update(Map map);
List<Student> findAll(Map map);
List<Student> findAll2(Map map);
List<Student> findAll3(Map map);
}
这是一个学生类的接口,按照规范,它的后缀是Maaper,并且它有一个映射文件,这个文件的名称应该是StudentMapper.xml,请尽量保持接口和xml文件的名称一致,这样即便使用不同方法注册mapper.xml配置文件时,也不会出现错误,并且请将两者放到同一个包下
2.6编写映射文件
写好接口之后,你需要去创建接口的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">
<mapper namespace="com.th.dao.StudentMapper">
<select id="findAll" resultType="com.th.pojo.Student">
select * from student
</select>
</mapper>
- 在这个配置文件中,namespace是你要映射的接口,这里映射的接口是
StudentMapper
,你必须使用完全限定名! - select标签是用来写查询语句的,其中id的值是你接口中的方法名,resultType则是你的返回类型!
- 增删改都有自己对应的标签!它们与查询类似!
- 编写完这个映射文件后,请到核心配置文件中的mappers标签中注册!千万不要忘记了!
- 注意:mybatis不支持方法重载!因为id是唯一的,如果方法重载,那么方法名必定相同,两个相同的id,会出现异常!但是不同的xml,里面的方法可以同名!详细情况请阅读此文章:https://blog.youkuaiyun.com/qq_34162294/article/details/108591515
2.7测试
使用单元测试:
@Test
public void t5() {
SqlSession sqlSession = MybatisUtils.getSqlSession(); //通过工具类获取连接
//获取接口的映射,相当于是在实例化对象
StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);
Map map=new HashMap();
map.put("tid",1);
List<Student> list = studentMapper.findAll2(map);
for (Student student : list) {
System.out.println(student);
}
sqlSession.close();//关闭连接
}
如果是增删改查,在关闭连接前还需要进行提交事务
sqlSession.commit(); //提交事务
3、核心文件
3.1、常用的配置
configuration(配置)
- properties(属性)
- settings(设置)
- typeAliases(类型别名)
- environments(环境配置)
- environment(环境变量)
- transactionManager(事务管理器)
- dataSource(数据源)
- environment(环境变量)
- mappers(映射器)
常用配置仅需掌握这几个就可以,如有其他需求,请到官方文档查看其余配置!
请注意:配置文件中的这些标签是有一个顺序的,请按照顺序来,否则会报错!
3.2、properties
你可以将核心xml文件的四个连接数据库的字段放到外部配置文件中。
<!--这是核心xml文件,使用properties标签导入了外部的数据库配置文件-->
<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>
//这是数据库配置文件,如果mysql版本在8.0以上,还需要加上时区
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&character=UTF-8
username=root
password=123
当然,你也可以将账号密码放入核心xml文件中,如下:
<properties resource="org/mybatis/example/config.properties">
<property name="username" value="root"/>
<property name="password" value="123"/>
</properties>
以上例子便是将账号密码放入xml核心配置文件中,请注意如果你在外部配置文件中也写了账号密码的属性,那么外部的配置文件会覆盖掉核心xml文件的配置!优先级:外部配置文件>核心配置文件
3.3、settings
常用的设置如下:
设置名 | 描述 | 有效值 | 默认值 |
---|---|---|---|
cacheEnabled | 全局性地开启或关闭所有映射器配置文件中已配置的任何缓存。 | true | false | true |
logImpl | 指定 MyBatis 所用日志的具体实现,未指定时将自动查找。 | SLF4J | LOG4J(deprecated since 3.5.9) | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING | 未设置 |
mapUnderscoreToCamelCase | 是否开启驼峰命名自动映射,即从经典数据库列名 A_COLUMN 映射到经典 Java 属性名 aColumn。 | true | false | False |
autoMappingBehavior | 指定 MyBatis 应如何自动映射列到字段或属性。 NONE 表示关闭自动映射;PARTIAL 只会自动映射没有定义嵌套结果映射的字段。 FULL 会自动映射任何复杂的结果集(无论是否嵌套)。 | NONE, PARTIAL, FULL | PARTIAL |
一个配置完整的 settings 元素的示例如下:
<settings>
<setting name="cacheEnabled" value="true"/>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="multipleResultSetsEnabled" value="true"/>
<setting name="useColumnLabel" value="true"/>
<setting name="useGeneratedKeys" value="false"/>
<setting name="autoMappingBehavior" value="PARTIAL"/>
<setting name="autoMappingUnknownColumnBehavior" value="WARNING"/>
<setting name="defaultExecutorType" value="SIMPLE"/>
<setting name="defaultStatementTimeout" value="25"/>
<setting name="defaultFetchSize" value="100"/>
<setting name="safeRowBoundsEnabled" value="false"/>
<setting name="mapUnderscoreToCamelCase" value="false"/>
<setting name="localCacheScope" value="SESSION"/>
<setting name="jdbcTypeForNull" value="OTHER"/>
<setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/>
</settings>
3.4、typeAliases
在编写mapper.xml文件时,你会发现resultType返回值写上全限定名是一件很麻烦的事情,这里,你可以给它取一个类型别名!
类型别名可为 Java 类型设置一个缩写名字。 它仅用于 XML 配置,意在降低冗余的全限定类名书写。例如:
<typeAliases>
<typeAlias type="com.th.pojo.Student" alias="student"/>
<typeAlias type="com.th.pojo.Teacher" alias="teacher"/>
</typeAliases>
这样配置,你就可以把全限定名改成你自己定义的类别名称了!
也可以指定一个包名,MyBatis 会在包名下面搜索需要的 Java Bean,比如:
<typeAliases>
<package name="com.th.pojo"/>
</typeAliases>
每一个在包 com.th.pojo
中的 Java Bean,在没有注解的情况下,会使用 Bean 的首字母小写的非限定类名来作为它的别名。
比如 com.th.pojo.Teacher
的别名为 teacher
;若有注解,则别名为其注解值。
注解别名是一个很好用的东西,你也许可以尝试使用它!
@Alias("author")
public class Author {
...
}
还有一些为常见的 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 |
3.5、environments
MyBatis 可以配置成适应多种环境,这种机制有助于将 SQL 映射应用于多种数据库之中, 现实情况下有多种理由需要这么做。例如,开发、测试和生产环境需要有不同的配置;或者想在具有相同 Schema 的多个生产数据库中使用相同的 SQL 映射。还有许多类似的使用场景。
不过要记住:尽管可以配置多个环境,但每个 SqlSessionFactory 实例只能选择一种环境!
3.6、mappers
你可以使用多种方法来注册MyBatis映射器!
推荐使用!
<!-- 使用相对于类路径的资源引用 -->
<mappers>
<mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
<mapper resource="org/mybatis/builder/BlogMapper.xml"/>
<mapper resource="org/mybatis/builder/PostMapper.xml"/>
</mappers>
使用注解增删改查时,需要使用以下方法,但这种注册方法需要保证接口和mapper.xml文件名保持一致,否则便找不到!
<!-- 使用映射器接口实现类的完全限定类名 -->
<mappers>
<mapper class="org.mybatis.builder.AuthorMapper"/>
<mapper class="org.mybatis.builder.BlogMapper"/>
<mapper class="org.mybatis.builder.PostMapper"/>
</mappers>
这种方法可以将包内的映射器全部注册!
<!-- 将包内的映射器接口实现全部注册为映射器 -->
<mappers>
<package name="org.mybatis.builder"/>
</mappers>
以下方法并不推荐使用!
<!-- 使用完全限定资源定位符(URL) -->
<mappers>
<mapper url="file:///var/mappers/AuthorMapper.xml"/>
<mapper url="file:///var/mappers/BlogMapper.xml"/>
<mapper url="file:///var/mappers/PostMapper.xml"/>
</mappers>
4、映射文件
4.1、顶级元素
SQL 映射文件只有很少的几个顶级元素(按照应被定义的顺序列出):
cache
– 该命名空间的缓存配置。cache-ref
– 引用其它命名空间的缓存配置。resultMap
– 描述查询结果集的字段和实体类属性的对应关系,是最复杂也是最强大的元素。sql
– 可被其它语句引用的可重用语句块。insert
– 映射插入语句。update
– 映射更新语句。delete
– 映射删除语句。select
– 映射查询语句。
4.2、单条件传参
如果仅传入一个基本类型或其他包装类型等,可以忽略掉parameterType属性,并且参数名也可以随意定义,如下:
接口:
public interface ThFinanceMapper {
ThFinance findId(String id);
}
映射文件:
<?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.th.mapper.ThFinanceMapper">
<select id="findId" resultType="ThFinance">
select * from th_finance where finance_id=#{aabb}
</select>
</mapper>
单元测试:
@Test
public void t1(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
ThFinanceMapper mapper = sqlSession.getMapper(ThFinanceMapper.class);
ThFinance finance = mapper.findId("2022/2/24");
System.out.println(finance.toString());
}
结果:
通常我们会使用==@Param()==注解,绑定参数,这样符合规范,关于@Param()将在下文讲解。
4.3、多条件传参
4.3.1、将参数作为Java对象传入
可以将java的对象作为参数传入,但是必须加上属性parameterType,并且参数名也必须与对象中的属性一致,如下:
实体类属性:
private String financeId; //这是实体类的属性名
private String userId;
private java.sql.Timestamp financeCreateDate;
private String financePayName;
private double financePayPrice;
接口:
public interface ThFinanceMapper {
ThFinance findThFinance(ThFinance thFinance);
}
映射文件:
<?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.th.mapper.ThFinanceMapper">
<select id="findThFinance" resultType="ThFinance" parameterType="ThFinance">
select * from th_finance
where finance_id=#{financeId} and finance_payName=#{financePayName}
</select>
</mapper>
单元测试:
@Test
public void t2(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
ThFinanceMapper mapper = sqlSession.getMapper(ThFinanceMapper.class);
//对象作为参数
ThFinance thFinance = new ThFinance();
thFinance.setFinanceId("2022/2/24");
thFinance.setFinancePayName("早餐");
ThFinance finance = mapper.findThFinance(thFinance);
System.out.println(finance.toString());
}
结果:
4.3.2、将参数封装成Map对象传入
如果传入的参数很零散,并不是一个对象,那么我们可以将它们放到map集合中,把map作为参数,同样的parameterType也是必不可少的,但是需要注意的是,参数名称是map的key值:
接口:
public interface ThFinanceMapper {
ThFinance mapThFinance(Map map);
}
映射文件:
<?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.th.mapper.ThFinanceMapper">
<select id="mapThFinance" resultType="ThFinance" parameterType="map">
select * from th_finance where finance_id=#{id} and finance_payName=#{name}
</select>
</mapper>
单元测试:
@Test
public void t3(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
ThFinanceMapper mapper = sqlSession.getMapper(ThFinanceMapper.class);
//参数名称是map的key
Map map = new HashMap();
map.put("id","2022/2/24");
map.put("name","早餐");
ThFinance finance = mapper.mapThFinance(map);
System.out.println(finance.toString());
}
结果:
4.3.3、@Param传参
@Param是MyBatis所提供的(org.apache.ibatis.annotations.Param),作为Dao层的注解,作用是用于传递参数,从而可以与SQL中的的字段名相对应,一般在2=<参数数<=5时使用最佳。
使用Map传参的缺点就在于可读性差,每次必须阅读他的键,才能明白其中的作用,并且不能限定其传递的数据类型,下面是使用@Param的情况:
接口:
public interface ThFinanceMapper {
//在这里,@Param("id")与参数financeId绑定,@Param("name")与参数payName绑定
ThFinance paramThFinance(@Param("id") String financeId,@Param("name") String payName);
}
映射文件:
<?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.th.mapper.ThFinanceMapper">
<!--在这里,我们使用@Param所绑定的参数,并且省略parameterType-->
<select id="paramThFinance" resultType="ThFinance">
select * from th_finance where finance_id=#{id} and finance_payName=#{name}
</select>
</mapper>
单元测试:
@Test
public void t4(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
ThFinanceMapper mapper = sqlSession.getMapper(ThFinanceMapper.class);
ThFinance finance = mapper.paramThFinance("2022/2/24","早餐");
System.out.println(finance.toString());
}
结果:
4.3.4、根据下标传参
除了以上几种,我们还可以根据下标传递参数,但是这种方法并不推荐使用,因为可读性要比使用map集合还要更差:
接口:
public interface ThFinanceMapper {
//注意,这里我们并没有使用@Param
ThFinance indexThFinance(String financeId,String payName);
}
映射文件:
<?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.th.mapper.ThFinanceMapper">
<!--同样的,我们省略parameterType,但是参数却变成了下标:arg0和arg1,分别代表第一个参数和第二个参数-->
<select id="indexThFinance" resultType="ThFinance">
select * from th_finance where finance_id=#{arg0} and finance_payName=#{arg1}
</select>
</mapper>
单元测试:
@Test
public void t5(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
ThFinanceMapper mapper = sqlSession.getMapper(ThFinanceMapper.class);
ThFinance finance = mapper.indexThFinance("2022/2/24","早餐");
System.out.println(finance.toString());
}
结果:
4.4、结果集映射
在一些复杂的查询之中,我们会遇到一个对象里面包含另一个对象,或者遇到一个对象里面包含一个集合,甚至更加复杂的结果,这个时候单凭简单的实体类对象是无法满足我们的要求的,所以我们需要进行结果集映射!
4.4.1简单理解
- 对于复杂的结果集,都需要使用resultMap进行结果集映射
- 它们都有两种查询处理方式:按照查询嵌套处理,按照结果嵌套处理
- 按照查询嵌套处理:类似于子查询
- 按照结果嵌套处理:类似于联表查询
4.4.2、相关属性或标签
association:对象里面套对象时,使用association标签
属性:
- property:实体类中需要进行映射的属性名称
- javaType:property属性的类型
- select:在使用按照查询嵌套处理时,结果集映射中需要使用到select属性来指定子查询的id
子元素:
- id:通常用来作为主键的映射,可以极大的提高Mybatis的查询效率
- result:普通的属性映射,将实体类的property与column联系起来
collection:对象里面套集合时,使用collection标签
属性:
- property:实体类中需要进行映射的属性名称
- ofType:用来指定映射到List或者集合中的pojo类型,泛型中的约束类型!
- select:在使用按照查询嵌套处理时,结果集映射中需要使用到select属性来指定子查询的id
子元素:
- id:通常用来作为主键的映射,可以极大的提高Mybatis的查询效率
- result:普通的属性映射,将实体类的property与column联系起来
4.4.3、一对多实例
按照结果嵌套处理
<select id="findALll" resultMap="studentTea">
SELECT s.`name` sname,s.age sage,t.`name` tname
from student s,teacher t
where s.tid=t.id
</select>
<!--property是实体类的字段,column是查询出来的结果集的列名,而非表名-->
<resultMap id="studentTea" type="student">
<result property="name" column="sname"/>
<result property="age" column="sage"/>
<association property="teacher" javaType="Teacher">
<result property="name" column="tname"/>
</association>
</resultMap>
按照查询嵌套处理
<select id="findALll" resultMap="studentTea">
select * from student
</select>
<!--这里使用select引用了getTeacher方法-->
<resultMap id="studentTea" type="student">
<association property="teacher" column="tid" javaType="Teacher" select="getTeacher"/>
</resultMap>
<select id="getTeacher" resultType="teacher">
select * from teacher where id=#{id}
</select>
4.4.4、多对一实例
按照结果套处理
<select id="getTeacher" resultMap="TeacherStu">
select s.id sid,t.id tid,t.name tname,s.name sname,s.age sage from student s,teacher t
where s.tid=t.id and t.id=#{id}
</select>
<resultMap id="TeacherStu" type="Teacher">
<result property="name" column="tname"/>
<result property="id" column="tid"/>
<collection property="students" ofType="student">
<result property="id" column="sid"/>
<result property="name" column="sname"/>
<result property="age" column="tid"/>
</collection>
</resultMap>
按照查询嵌套处理
<select id="getTeacher" resultMap="TeacherStu">
select * from teacher where id=#{id}
</select>
<resultMap id="TeacherStu" type="teacher">
<collection property="students" javaType="ArrayList" ofType="student" select="getTeacherStu" column="id"/>
</resultMap>
<select id="getTeacherStu" resultType="student">
select * from student where tid=#{tid}
</select>
4.4.5、小结
- 我们可以在collection和association中使用resultMap进行结果集的映射,这样极大的增加了重用性!
- 为了保证映射的正确,column属性不能重复!!!
- resultMap和resultType的在本质上是一样的!在select中,两者不能同时使用!
4.5、自动映射
在做结果集映射时,我们需要注意的是,如果使用了collection和association,那么自动映射将会失效,我们必须手动将全部的属性和查询结果的column关联起来,当然,我们也可以在核心配置文件中设置autoMappingBehavior的属性,将它设置为FULL!
4.6、查询
<select id="findId" resultType="ThFinance">
select * from th_finance where finance_id=#{aabb}
</select>
模糊查询:
<!--两种方式都可以-->
<select id="indexThFinance" resultType="ThFinance">
select * from th_finance where finance_payName like "%"#{name}"%"
</select>
<select id="likeThFinance" resultType="ThFinance">
select * from th_finance where finance_payName like concat('%',#{name},'%')
</select>
分页:
select 元素的属性
属性 | 描述 |
---|---|
id | 在命名空间中唯一的标识符,可以被用来引用这条语句。 |
parameterType | 将会传入这条语句的参数的类全限定名或别名。这个属性是可选的,因为 MyBatis 可以通过类型处理器(TypeHandler)推断出具体传入语句的参数,默认值为未设置(unset)。 |
parameterMap | 用于引用外部 parameterMap 的属性,目前已被废弃。请使用行内参数映射和 parameterType 属性。 |
resultType | 期望从这条语句中返回结果的类全限定名或别名。 注意,如果返回的是集合,那应该设置为集合包含的类型,而不是集合本身的类型。 resultType 和 resultMap 之间只能同时使用一个。 |
resultMap | 对外部 resultMap 的命名引用。结果映射是 MyBatis 最强大的特性,如果你对其理解透彻,许多复杂的映射问题都能迎刃而解。 resultType 和 resultMap 之间只能同时使用一个。 |
flushCache | 将其设置为 true 后,只要语句被调用,都会导致本地缓存和二级缓存被清空,默认值:false。 |
useCache | 将其设置为 true 后,将会导致本条语句的结果被二级缓存缓存起来,默认值:对 select 元素为 true。 |
timeout | 这个设置是在抛出异常之前,驱动程序等待数据库返回请求结果的秒数。默认值为未设置(unset)(依赖数据库驱动)。 |
fetchSize | 这是一个给驱动的建议值,尝试让驱动程序每次批量返回的结果行数等于这个设置值。 默认值为未设置(unset)(依赖驱动)。 |
statementType | 可选 STATEMENT,PREPARED 或 CALLABLE。这会让 MyBatis 分别使用 Statement,PreparedStatement 或 CallableStatement,默认值:PREPARED。 |
resultSetType | FORWARD_ONLY,SCROLL_SENSITIVE, SCROLL_INSENSITIVE 或 DEFAULT(等价于 unset) 中的一个,默认值为 unset (依赖数据库驱动)。 |
databaseId | 如果配置了数据库厂商标识(databaseIdProvider),MyBatis 会加载所有不带 databaseId 或匹配当前 databaseId 的语句;如果带和不带的语句都有,则不带的会被忽略。 |
resultOrdered | 这个设置仅针对嵌套结果 select 语句:如果为 true,将会假设包含了嵌套结果集或是分组,当返回一个主结果行时,就不会产生对前面结果集的引用。 这就使得在获取嵌套结果集的时候不至于内存不够用。默认值:false 。 |
resultSets | 这个设置仅适用于多结果集的情况。它将列出语句执行后返回的结果集并赋予每个结果集一个名称,多个名称之间以逗号分隔。 |
4.7、增删改
数据变更语句 insert,update 和 delete 的实现非常接近:
<insert id="insert" parameterType="com.th.pojo.Employee">
insert employee(emp_name,emp_desc) values (#{empName},#{empDesc});
</insert>
<update id="update" parameterType="com.th.pojo.Employee">
update employee set emp_name=#{empName},emp_desc=#{empDesc} where id=#{id}
</update>
<delete id="delete" parameterType="int">
delete from employee where id=#{id}
</delete>
Insert, Update, Delete 元素的属性
属性 | 描述 |
---|---|
id | 在命名空间中唯一的标识符,可以被用来引用这条语句。 |
parameterType | 将会传入这条语句的参数的类全限定名或别名。这个属性是可选的,因为 MyBatis 可以通过类型处理器(TypeHandler)推断出具体传入语句的参数,默认值为未设置(unset)。 |
parameterMap | 用于引用外部 parameterMap 的属性,目前已被废弃。请使用行内参数映射和 parameterType 属性。 |
flushCache | 将其设置为 true 后,只要语句被调用,都会导致本地缓存和二级缓存被清空,默认值:(对 insert、update 和 delete 语句)true。 |
timeout | 这个设置是在抛出异常之前,驱动程序等待数据库返回请求结果的秒数。默认值为未设置(unset)(依赖数据库驱动)。 |
statementType | 可选 STATEMENT,PREPARED 或 CALLABLE。这会让 MyBatis 分别使用 Statement,PreparedStatement 或 CallableStatement,默认值:PREPARED。 |
useGeneratedKeys | (仅适用于 insert 和 update)这会令 MyBatis 使用 JDBC 的 getGeneratedKeys 方法来取出由数据库内部生成的主键(比如:像 MySQL 和 SQL Server 这样的关系型数据库管理系统的自动递增字段),默认值:false。 |
keyProperty | (仅适用于 insert 和 update)指定能够唯一识别对象的属性,MyBatis 会使用 getGeneratedKeys 的返回值或 insert 语句的 selectKey 子元素设置它的值,默认值:未设置(unset )。如果生成列不止一个,可以用逗号分隔多个属性名称。 |
keyColumn | (仅适用于 insert 和 update)设置生成键值在表中的列名,在某些数据库(像 PostgreSQL)中,当主键列不是表中的第一列的时候,是必须设置的。如果生成列不止一个,可以用逗号分隔多个属性名称。 |
databaseId | 如果配置了数据库厂商标识(databaseIdProvider),MyBatis 会加载所有不带 databaseId 或匹配当前 databaseId 的语句;如果带和不带的语句都有,则不带的会被忽略。 |
4.8、SQL片段
可以将某些重用的SQL语句提取出来,使用include标签去引用,实现代码的复用:
<!--将需要提取的SQL语句,放到sql标签中-->
<sql id="th"> where valid = #{id} </sql>
<!--在需要使用的地方用include标签引用-->
<select id = 'findId'>select * from user <include refid = 'th'></include>
注意:
- 最好基于单表来定义SQL片段,不要做太复杂的事情,否则重用率会大大降低!
4.9、mybatis中#和$的区别
1、#
将传入的数据都当成一个字符串,会对自动传入的数据加一个双引号。如:
where id=#{id},如果传入的值是1,那么解析成sql时的值为where id=“1”
如果传入的值是abc,则解析成的sql为where id=“abc”。
2、$
将传入的数据直接显示生成在sql中。
如:where id=${id},如果传入的值是1,那么解析成sql时的值为where username=1;
如果传入的值是;drop table user;,则解析成的sql为:
select * from student where id=1;drop table student;
3、#
方式能够很大程序防止sql注入,$
方式无法防止sql注入。
4、$
方式一般用于传入数据库对象,比如表名。
5、一般能用#
的就不要使用$
,若不得不使用,则要做好前期校验工作,防止sql注入攻击。
6、在mybatis中,涉及到动态表名和列名时,只能使用${xxx}
这样的参数形式。所以这样的参数需要我们在代码中手工进行处理来防止注入。
5、动态SQL
动态SQL与JSTL标签类似,它根据不同的条件生成不同的SQL语句。
if
<select id="findAll" parameterType="map" resultType="student">
select * from student where 1=1
<if test="name!=null">
and name=#{name}
</if>
<if test="tid!=null">
and tid=#{tid}
</if>
</select>
choose(when,otherwise)
MyBatis 提供了 choose 元素,它有点像 Java 中的 switch 语句。
<select id="findAll2" parameterType="map" resultType="student">
select * from student
<where>
<choose>
<when test="tid!=null">
tid=#{tid}
</when>
<when test="name!=null">
name=#{name}
</when>
</choose>
</where>
</select>
trim(where,set)
trim
trim相当于where和set的父级,通过自定义 trim 元素来定制 where 元素的功能。比如,和 where 元素等价的自定义 trim 元素为:
<trim prefix="WHERE" prefixOverrides="AND |OR ">
...
</trim>
prefixOverrides 属性会忽略指定符号分隔的文本序列(注意此例中的空格是必要的)。上述例子会移除所有 prefixOverrides 属性中指定的内容,并且插入 prefix 属性中指定的内容。
where
where 元素只会在子元素返回任何内容的情况下才插入 “WHERE” 子句。而且,若子句的开头为 “AND” 或 “OR”,where 元素也会将它们去除。
如果 where 元素与你期望的不太一样,你也可以通过自定义 trim 元素来定制 where 元素的功能。
<select id="findAll" parameterType="map" resultType="student">
select * from student
<where>
<if test="name!=null">
and name=#{name}
</if>
<if test="tid!=null">
and tid=#{tid}
</if>
</where>
</select>
set
用于动态更新语句的类似解决方案叫做 set。set 元素可以用于动态包含需要更新的列,忽略其它不更新的列。
<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>
<update id="update" parameterType="map">
update student
<set>
tid=#{tid},
</set>
<where>
id=#{id}
</where>
</update>
以上例子中,set 元素会动态地在行首插入 SET 关键字,并会删掉额外的逗号(这些逗号是在使用条件语句给列赋值时引入的),比如最后一个要修改的参数为空,那么SQL语句就会多出一个逗号,而set元素会自动删除这个多余的逗号,所以尽情的去写逗号,不需要有所顾虑。
foreach
动态 SQL 的另一个常见使用场景是对集合进行遍历(尤其是在构建 IN 条件语句的时候)。
<select id="selectPostIn" resultType="domain.blog.Post">
SELECT *
FROM POST P
<where>
<foreach item="item" index="index" collection="list"
open="ID in (" separator="," close=")" nullable="true">
#{item}
</foreach>
</where>
</select>
以上例子实际SQL语句为:SELECT * FROM POST P where 1=1 and ID in (#{item},#{item},#{item});
in条件实际可能会更多,但通过遍历集合,便可将所有条件全部加入到in条件中!
<select id="findAll3" parameterType="map" resultType="student">
select * from student
<where>
<foreach collection="ids" item="id" open="and (" close=")" separator="or">
id=#{id}
</foreach>
</where>
</select>
以上例子实际SQL语句为:select * from student where (id=#{id} or id=#{id} or id=#{id})
foreach 元素的功能非常强大,它允许你指定一个集合,声明可以在元素体内使用的集合项(item)和索引(index)变量。它也允许你指定开头与结尾的字符串以及集合项迭代之间的分隔符。这个元素也不会错误地添加多余的分隔符
提示
你可以将任何可迭代对象(如 List、Set 等)、Map 对象或者数组对象作为集合参数传递给 foreach。当使用可迭代对象或者数组时,index 是当前迭代的序号,item 的值是本次迭代获取到的元素。当使用 Map 对象(或者 Map.Entry 对象的集合)时,index 是键,item 是值。
bind
<!-- bind可以创建一个变量并将其绑定到上下文 -->
<select id="selectUser" resultType="user">
<bind name="pattern" value="'%' + _parameter.getTitle() + '%'" />
SELECT * FROM User
WHERE name LIKE #{pattern}
</select>
总结
-
动态SQL就是拼接SQL语句,只要保证SQL的正确性,按照SQL的语法,去进行各种组合就可以了!
-
可以先在mysql中写出完整的SQL语句,然后再去拆分,修改成为动态SQL!
6、注解SQL
使用注解来映射简单语句会使代码显得更加简洁,但对于稍微复杂一点的语句,Java 注解不仅力不从心,还会让你本就复杂的 SQL 语句更加混乱不堪。 因此,如果你需要做一些很复杂的操作,最好用 XML 来映射语句。
使用注解CRUD,是没有mapper.xml文件的,所以你需要使用全限定类名的方式去注册!
@Select("select * from teacher")
List<Teacher> findAll();
如果有参数,那么需要加上@param(),将参数传递过去!
//请注意,@param("id")中的id与#{id}中的id需要相同
@Select("select * from employee where id=#{id}")
Employee findId(@Param("id") int id);
当你的条件是对象是,则自动匹配,如下:
//你需要将对象中的字段名与#{}中的值相对应,这样才能匹配的上
@Insert("insert employee values(null,#{name},#{desc})")
int insert(Employee employee);
还有一些其他的注解,他们并不是增删改,仅作为一个补充,了解即可!
@SuppressWarnings("all") //抑制警告
另外,在企业中,常用UUID生成的随机数来作为id主键:
UUID.randomUUID().toString().replaceAll("-","");//replaceAll("-","")会移除掉中间的“-”
7、缓存
简介
后续会用到Redis缓存,Mybatis缓存了解即可
什么是缓存?
- 缓存是存储在内存中的临时数据
为什么使用缓存?
- 将用户经常查询的数据放到内存中,就不用去数据库读取数据,可以有效减少和数据库的交互次数,减少系统开销,提高系统效率,解决高并发系统的性能问题。
-
MyBatis 内置了一个强大的事务性查询缓存机制,它可以非常方便地配置和定制。
-
MyBatis 有两级缓存:一级缓存和二级缓存
-
默认情况下,只启用了本地的会话缓存,它仅仅对一个会话中的数据进行缓存。(SqlSession级别)
-
要启用全局的二级缓存,只需要在你的 SQL 映射文件中添加一行:
<cache/>
这个简单语句的效果如下:
- 映射语句文件中的所有 select 语句的结果将会被缓存。
- 映射语句文件中的所有 insert、update 和 delete 语句会刷新缓存。
- 缓存会使用最近最少使用算法(LRU, Least Recently Used)算法来清除不需要的缓存。
- 缓存不会定时进行刷新(也就是说,没有刷新间隔)。
- 缓存会保存列表或对象(无论查询方法返回哪种)的 1024 个引用。
- 缓存会被视为读/写缓存,这意味着获取到的对象并不是共享的,可以安全地被调用者修改,而不干扰其他调用者或线程所做的潜在修改。
- 提示 缓存只作用于 cache 标签所在的映射文件中的语句。如果你混合使用 Java API 和 XML 映射文件,在共用接口中的语句将不会被默认缓存。你需要使用 @CacheNamespaceRef 注解指定缓存作用域。
可用的清除策略有:
- LRU` – 最近最少使用:移除最长时间不被使用的对象。
FIFO
– 先进先出:按对象进入缓存的顺序来移除它们。SOFT
– 软引用:基于垃圾回收器状态和软引用规则移除对象。WEAK
– 弱引用:更积极地基于垃圾收集器状态和弱引用规则移除对象。- 默认的清除策略是 LRU。
一级缓存
测试
- 开启日志!
- 测试在一个Session中查询两次相同记录
- 查询日志输出
@Test
public void t4() {
SqlSession sqlSession = MybatisUtils.getSqlSession();
StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);
Map map=new HashMap();
map.put("name","张三");
List<Student> list = studentMapper.findAll(map);
for (Student student : list) {
System.out.println(student);
}
System.out.println("===========================================");
List<Student> list2 = studentMapper.findAll(map);
for (Student student : list2) {
System.out.println(student);
}
sqlSession.close();
}
缓存失效情况
- 查询不同的东西
-
增删改操作,可能会改变原来的数据,所以必定会刷新缓存
-
使用不同的Mapper.xml也会使缓存失效
-
手动清理缓存
sqlSession.clearCache();
二级缓存
- 二级缓存也叫全局缓存,因为一级缓存作用域太低了,所以诞生了二级缓存
- 基于namespace级别的缓存,一个名称空间,对应一个二级缓存
- 工作机制
- 一个会话查询一条数据,这个数据就会被放在当前会话的一级缓存中
- 如果当前会话关闭了,这个会话对应的一级缓存就没了,但是我们需要的是,会话关闭了,一级缓存中的数据被保存到二级缓存中
- 新的会话查询信息,就可以从二级缓存中获取内容;
- 不同的mapper查出的数据会放在自己对应的缓存(map)中
步骤:
-
开启全局缓存
<!--虽然全局缓存默认自动开启,但你仍需要显示的开启全局缓存,这是一种编程规范--> <setting name="cacheEnabled" value="true"/>
-
要在使用二级缓存的Mapper中开启
<!--在当前Mapper.xml中使用二级缓存--> <cache/>
如果你没有序列化,那么就会报错: Cause: java.io.NotSerializableException: com.th.pojo.Student
你可以把它的详细参数写出来,这样即便你的实体类没有序列化,也仍旧可以运行
<cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true"/>
注意
实体类最好序列化,序列化只需要实现Serializable接口即可
implements Serializable
小结
- 只要开启了二级缓存,在同一个Mapper下就有效
- 所有的数据都会先放在一级缓存中
- 只要当会话提交,或者关闭的时候,才会提交到二级缓存中
8、拓展
反射的概念是由Smith在1982年首次提出的,主要是指程序可以访问、检测和修改其本身状态或行为的一种能力。
在 Java 环境中,反射机制允许程序在执行时获取某个类自身的定义信息,如属性和方法等,也可以实现动态创建类的对象、变更属性的内容或执行特定的方法的功能。从而使 Java 具有动态语言的特性,增强了程序的灵活性和可移植性。
Mybatis的反射是一个特别强大的模块。比如:
在mybaits中配置 useGeneratedKeys=“true” ,主键是自动生成,那么在==appVersionDao.insert(appVersion);==这一步后,Mybatis会自动生成主键并设置到appVersion里面,所以在同一个事务中,可以直接通过appVersion.getId获取到主键,而不需要额外查询一次!