MyBatis 是支持普通 SQL 查询,存储过程和高级映射的优秀持久层框架。
MyBatis 消除了几乎所有的 JDBC 代码和参数的手工设置以及结果集的检索。
MyBatis 使用简单的 XML 或注解用于配置和原始映射,将接口和 Java 的POJOs(普通的 Java对象)映射成数据库中的记录。
每个MyBatis应用程序主要都是使用SqlSessionFactory实例的,一个SqlSessionFactory实例可以通过SqlSessionFactoryBuilder获得。SqlSessionFactoryBuilder可以从一个xml配置文件或者一个预定义的配置类的实例获得。
用xml文件构建SqlSessionFactory实例是非常简单的事情。推荐在这个配置中使用类路径资源(classpath resource),但其实可以使用任何Reader实例,包括用文件路径或 file:// 开头的 url 创建的实例。MyBatis有一个实用类----Resources,它有很多方法,可以方便地从类路径及其它位置加载资源。
优点
缺点
依赖
SpringBoot官方并没有提供Mybatis的启动器,不过Mybatis官网自己实现了
<dependency>
<groupId>org.mybatis.spring.bootgroupId>
<artifactId>mybatis-spring-boot-starterartifactId>
<version>2.1.3version>
dependency>
<dependency>
<groupId>com.github.pagehelpergroupId>
<artifactId>pagehelper-spring-boot-starterartifactId>
<version>1.2.3version>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-jdbcartifactId>
dependency>
默认情况下,插件MyBatis-Spring-Boot-Starter将进行如下配置:
自动检查 SpringBoot 的数据源配置并构建 DataSource 对象
通过 SqlSessionFactoryBean 使用数据源构建并注册 SqlSessionFactory 对象
从 SqlSessionFactory 中创建并注册一个 SqlSessionTemplate 实例,其实就是构建一个 SqlSession 对象
自动扫描使用了注解@Mapper
的接口映射器,并将这些映射器与SqlSessionTemplate实例进行关联,同时将它们注册到Spring容器中
常用yaml配置
mybatis:
# 注册XML映射器,即mapper.xml文件位置。如果没有映射文件,请注释掉
mapper-locations: classpath:mapper/**.xml
# 配置Java类型别名包扫描路径。通过该属性可以给包中的类注册别名,注册后在 Mapper 对应的 XML 文件中可以直接使用类名,而不用使用全限定的类名(即 XML 中调用的时候可以不用包含全限包名)
type-aliases-package: com.test.springbootssm.entity
configuration:
# 指定MyBatis所用日志的具体实现(输出sql语句),未指定时将自动查找。
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
# 开启自动驼峰命名规则(camel case)映射。即从经典数据库列名 A_COLUMN(下划线命名)到经典 Java 属性名 aColumn(驼峰命名)的类似映射
map-underscore-to-camel-case: true
不太常用的yaml配置
mybatis:
## 不常用的配置
check-config-location: true # 是否检测MyBatis运行参数配置文件
config-location: classpath:mybatis/mybatis-config.xml # mybatis配置文件所在路径
type-handlers-package: test.springboot.handlers # 配置类型处理器包名
executor-type: SIMPLE # 指定执行器类型
configuration:
default-fetch-size: 20
default-statement-timeout: 30
lazy-loading-enabled: true # 开启延时加载开关
aggressive-lazy-loading: false # 将积极加载改为消极加载(即按需加载),默认值就是false
lazy-load-trigger-methods: "" # 指定触发延迟加载的方法
cache-enabled: true # 打开全局缓存开关(二级环境),默认值就是true
#MyBatis使用pageHelper分页
pagehelper:
helper-dialect: mysql # 配置使用哪种数据库语言,不配置的话pageHelper也会自动检测
reasonable: true # 启用查询合理化。如果pageNum<1,则会查询第一页;如果pageNum>pages,则会查询最后一页
# 支持通过Mapper接口参数来传递分页参数,默认值false,分页插件会从查询方法的参数值中,自动根据上面 params 配置的字段中取值,查找到合适的值时就会自动分页。
support-methods-arguments: true
上述配置参数最终是通过mybatis-spring-boot-autoconfigure.jar加载和配置的。
Java方式配置MyBatis运行时参数
MyBatis的运行时参数除了可以在SpringBoot的配置文件中指定,还可以通过Java编码方式设置。实际上就是在Spring容器中注册一个实现了ConfigurationCustomizer接口的Bean。
import org.springframework.context.annotation.Configuration;
@Configuration
public class MyBatisConfig {
@Bean
ConfigurationCustomizer mybatisConfigurationCustomizer() {
return new ConfigurationCustomizer() {
@Override
public void customize(org.apache.ibatis.session.Configuration configuration) {
// 在SpringBoot中以Java编码方式配置MyBatis运行时参数
configuration.setMapUnderscoreToCamelCase(true);
configuration.addMappers("com.test.springboot.mapper");
}
};
}
}
依赖
<dependency>
<groupId>org.mybatisgroupId>
<artifactId>mybatisartifactId>
<version>${version.mybatis}version>
dependency>
<dependency>
<groupId>tk.mybatisgroupId>
<artifactId>mapperartifactId>
<version>${version.mybatis.mapper}version>
dependency>
<dependency>
<groupId>com.github.pagehelpergroupId>
<artifactId>pagehelperartifactId>
<version>${version.pagehelper}version>
dependency>
<dependency>
<groupId>org.mybatisgroupId>
<artifactId>mybatis-springartifactId>
<version>${version.mybatis.spring}version>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-txartifactId>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-jdbcartifactId>
dependency>
通过Java方式注册MyBatis核心组件
通过Java方式在Spring框架中注册MyBatis的核心组件Bean,并且配置声明式事务管理。
(1)在Spring中注册MyBatis的核心组件Bean:SqlSessionFactory,SqlSession,以及Spring的事务管理器。另外,在构建SqlSessionFactory时还可以注册MyBatis的xml映射器。
@Configuration
@EnableTransactionManagement
public class MyBatisSpringConfig implements TransactionManagementConfigurer {
@Autowired
private DataSource dataSource;
// 在Spring中注册SqlSessionFactory,在这里可以设置一下参数:
// 1.设置分页参数
// 2.配置MyBatis运行时参数
// 3.注册xml映射器
@Bean
public SqlSessionFactory sqlSessionFactory() {
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
// 设置数据源
sqlSessionFactoryBean.setDataSource(dataSource);
// 设置映射POJO对象包名
// sqlSessionFactoryBean.setTypeAliasesPackage("org.chench.test.springboot.model");
// 分页插件
/*PageHelper pageHelper = new PageHelper();
Properties properties = new Properties();
properties.setProperty("reasonable", "true");
properties.setProperty("supportMethodsArguments", "true");
properties.setProperty("returnPageInfo", "check");
properties.setProperty("params", "count=countSql");
pageHelper.setProperties(properties);*/
//添加插件
//sqlSessionFactoryBean.setPlugins(new Interceptor[]{pageHelper});
// 配置mybatis运行时参数
org.apache.ibatis.session.Configuration configuration = new org.apache.ibatis.session.Configuration();
// 自动将数据库中的下划线转换为驼峰格式
configuration.setMapUnderscoreToCamelCase(true);
configuration.setDefaultFetchSize(100);
configuration.setDefaultStatementTimeout(30);
sqlSessionFactoryBean.setConfiguration(configuration);
// 在构建SqlSessionFactory时注册xml映射器
ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
try {
sqlSessionFactoryBean.setMapperLocations(resolver.getResources("classpath:mapper/*.xml"));
return sqlSessionFactoryBean.getObject();
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
/**
* 注入sqlSession对象
* @param sqlSessionFactory
* @return
*/
@Bean(value = "sqlSession")
public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
return new SqlSessionTemplate(sqlSessionFactory);
}
// Spring事务管理器
@Bean(value = "transactionManager")
@Override
public PlatformTransactionManager annotationDrivenTransactionManager() {
return new DataSourceTransactionManager(dataSource);
}
}
(2)注册MyBatis接口映射器 MyBatis 3支持2种映射器:xml映射器和接口映射器,其中xml映射器可以在构建SqlSessionFactory时进行注册。
@Configuration
@AutoConfigureAfter(MyBatisSpringConfig.class) //注意,由于MapperScannerConfigurer执行的比较早,所以必须有该注解
public class MyBatisMapperScannerConfig {
@Bean
public MapperScannerConfigurer mapperScannerConfigurer() {
MapperScannerConfigurer mapperScannerConfigurer = new MapperScannerConfigurer();
// 设置sqlSessionFactory名
mapperScannerConfigurer.setSqlSessionFactoryBeanName("sqlSessionFactory");
// 设置接口映射器基础包名
mapperScannerConfigurer.setBasePackage("org.chench.test.springboot.mapper");
Properties properties = new Properties();
//properties.setProperty("mappers", "org.chench.test.springboot.mapper");
properties.setProperty("notEmpty", "false");
properties.setProperty("IDENTITY", "MYSQL");
mapperScannerConfigurer.setProperties(properties);
return mapperScannerConfigurer;
}
}
MyBatis支持2种类型的映射器:XML映射器和接口映射器,在这里以定义并使用接口映射器为例。
定义接口映射器
@Repository
public interface AccountMapper {
@Select("select * from account where id = #{id}")
public Account getAccountById(@Param("id") long id);
}
注意: 在这里可以使用Spring容器的注解@Repository
声明MyBatis的接口映射器为一个Bean组件,这样在使用接口映射器时可以直接注入这个接口映射器Bean进行使用。
通过xml方式注册MyBatis核心组件
applicationContext-mybaits.xml
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="username" value="root">property>
<property name="password" value="root">property>
<property name="url" value="jdbc:mysql:///heima23">property>
<property name="driverClassName" value="com.mysql.jdbc.Driver">property>
bean>
<bean id="sqlSessionfactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource">property>
<property name="typeAliasesPackage" value="cn.test.domain">property>
bean>
<bean id="mapperScannerConfigurer" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="cn.test.dao">property>
bean>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource">property>
bean>
<tx:advice id="txAdvice">
<tx:attributes>
<tx:method name="save*" propagation="REQUIRED" read-only="false"/>
<tx:method name="update*"/>
<tx:method name="delete*"/>
<tx:method name="find*" propagation="SUPPORTS" read-only="true"/>
<tx:method name="*">tx:method>
tx:attributes>
tx:advice>
<aop:config>
<aop:pointcut id="pt" expression="execution(* cn.test.service.impl.*.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="pt">aop:advisor>
aop:config>
beans>
DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.test.springbootssm.mapper.UserMapper">
<select id="findById" parameterType="long" resultType="user">
select * from tb_user where id=#{id}
select>
mapper>
方式一
给每一个Mapper接口添加@Mapper
注解,由Spring来扫描这些注解,完成Mapper接口的动态代理。
@Mapper
public interface UserMapper {
}
方式二
在启动类上添加扫描包注解(若Mapper接口文件位置统一存放,推荐此种方式)
这种方式的好处是,不用给每一个Mapper都添加注解。
@SpringBootApplication
@MapperScan("com.test.springbootssm.mapper")
public class Application {
public static void main(String[] args) {
// 启动代码
SpringApplication.run(Application.class, args);
}
}
接口开发规范
UserMapper接口
public interface UserMapper {
public List<User> findAll();
}
UserMapper.xml
DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.test.mapper.UserMapper">
<select id="findAll" resultType="user">
select * from user
select>
mapper>
resultType
如果实体的属性名与数据库表中字段名(或查询字段别名)一致,将查询结果自动封装到实体类中。
resutlMap
如果实体的属性名与数据库表中字段名不一致,使用ResutlMap实现手动映射封装到实体类中
resutlMap的属性说明 :
<resultMap id="userResultMap" type="user">
<id column="uid" property="id">id>
<result column="username" property="username">result>
xml映射文件:
<resultMap id="userResultMap" type="user">
<id column="uid" property="id">id>
<result column="username" property="username">result>
<result column="bir" property="birthday">result>
<result column="gender" property="sex">result>
<result column="address" property="address">result>
resultMap>
<select id="findAllResultMap" resultMap="userResultMap">
SELECT id AS uid,username AS NAME,birthday AS bir,sex AS gender ,address FROM USER
select>
方式1:将多个参数封装到一个 pojo对象中,进行对象的传递【推荐】
方式2:使用 @Param 注解声明参数【推荐】
缺点:参数过多时不太方便
方式3:xml里sql中使用 #{arg0} #{arg1}… 或 #{param1} #{param2}… 传递参数
缺点:可读性差
mapper接口:
// 方式1
public List<User> findByIdAndUsername3(User user);
// 方式2
public List<User> findByIdAndUsername2(@Param("id") Integer id,@Param("username") String username);
// 方式3
public List<User> findByIdAndUsername1(Integer id,String username);
xml文件:
<select id="findByIdAndUsername3" parameterType="user" resultType="user">
select * from user where id = #{id} and username = #{username}
select>
<select id="findByIdAndUsername2" resultType="user">
select * from user where id = #{id} and username = #{username}
select>
<select id="findByIdAndUsername1" resultType="user">
select * from user where id = #{param1} and username = #{param2}
select>
方式1:sql中使用 concat
函数进行模糊匹配拼接【推荐】
方式2:sql中使用 ||
(字符串连接符)进行模糊匹配拼接【推荐】
方式3:java中添加模糊匹配
缺点:在java中出现了sql通配符,产生了耦合性问题,不太推荐使用
// java示例
List<User> list = userDao.findLikeUsername("%王%")
方式4:sql中拼接模糊匹配(%#{usenme}%)
缺点:只支持mysl5.5以上版本,oracle不支持该写法
方式5:sql中拼接模糊匹配(%${value}%)
${}
字符串拼接 ,如果接收简单数据类型 名称只能是:${value}
缺点:这种方式的底层使用的是编译对象statement做的查询,会出现sql注入问题,开发不能使用
xml文件:
<select id="findByUsername4" parameterType="string" resultType="user">
select * from user where username like concat('%', #{username}, '%');
select>
<select id="findByUsername4" parameterType="string" resultType="user">
select * from user where username like ('%' || #{username} || '%');
select>
<select id="findByUsername1" parameterType="string" resultType="user">
select * from user where username like #{username}
select>
<select id="findByUsername1" parameterType="string" resultType="user">
select * from user where username like '%#{username}%'
select>
<select id="findByUsername3" parameterType="string" resultType="user">
select * from user where username like '%${value}%'
select>
需要添加 PageHelper 的依赖
<dependency>
<groupId>com.github.pagehelpergroupId>
<artifactId>pagehelper-spring-boot-starterartifactId>
<version>1.2.3version>
dependency>
/**
* 分页查询所有数据
* */
@Override
public PageInfo<Brand> findPage(Integer page, Integer size) {
//调用PageHelper的startPage方法,设置当前页、每页条数
PageHelper.startPage(page, size);
//调用dao接口方法,查询所有的数据
// List brandList = brandDao.selectAll();
Page<Brand> list = brandDao.selectAll();
//把查到的数据封装到PageInfo中,然后返回,PageInFo中包含了所有的数据,总条数等等
PageInfo<Brand> pageInfo = new PageInfo<>(list);
//返回即可
return pageInfo;
}
(1)#{}:占位符
#{} 是预编译处理,MyBatis在处理 #{} 时,它会将sql中的 #{} 替换为 ?,然后调用 PreparedStatement 的set方法来赋值,传入字符串后,会在值两边加上单引号。
(2)${}:拼接符
${} 是字符串替换,在处理是字符串替换,mybaits在处理时,它会将 sql 中的 { } 替换为变量的值,传入的数据不会在两边加上单引号。
注意:使用${ }会导致sql注入,不利于系统的安全性!
SQL注入:就是通过把SQL命令插入到Web表单提交或输入域名或页面请求的查询字符串,最终达到欺骗服务器执行恶意的SQL命令。常见的有匿名登录(在登录框输入恶意的字符串)、借助异常获取数据库信息等
#{} 与 ${} 的区别:
#{}:底层 PreparedStatement
1.sql与参数分离,不会出现sql注入问题
2.sql只需要编译一次
3.接收普通数据类型,命名:#{随便写}
4.接收引用数据类型,命名:#{属性名}
5.变量替换后,#{} 对应的变量自动加上单引号 ''
${}:底层 Statement
1.将sql与参数拼接在一起,会出现sql注入问题
2.每次执行sql语句都会编译一次
3.接收普通数据类型,命名:'${value}'
4.接收引用数据类型,命名: '${属性名}' ;注意:需要使用单引号‘${xxx}’
5.变量替换后,${} 对应的变量不会加上单引号 ''
1、方法有多个参数
原因:当不使用 @Param 注解时,mybatis 是不认识哪个参数叫什么名字的,尽管在接口中定义了参数的名称,mybatis仍然不认识。mybatis默认以接口中参数定义的顺序和SQL语句中的表达式进行映射。
例如:
mapper接口方法:
@Mapper
public interface UserMapper {
Integer insert(@Param("username") String username, @Param("address") String address);
}
对应的xml:
<insert id="insert" parameterType="org.javaboy.helloboot.bean.User">
insert into user (username, address) values (#{username}, #{address});
insert>
2、方法参数要取别名
mapper接口方法:
@Mapper
public interface UserMapper {
Integer insert(@Param("name") String username, @Param("address") String address);
}
对应的xml:
<insert id="insert" parameterType="org.javaboy.helloboot.bean.User">
insert into user (username, address) values (#{name}, #{address});
insert>
3、XML 中的 SQL 使用了 $ 拼接sql
$
会有注入的问题,但是有的时候不得不使用 $ 符号,例如要传入列名或者表名的时候,这个时候必须要添加 @Param 注解
mapper接口方法:
@Mapper
public interface UserMapper {
List<User> getAllUsers(@Param("order_by")String order_by);
List<User> findAll(@Param("tb_user")String tb_user);
}
对应xml:
<select id="getAllUsers" resultType="org.javaboy.helloboot.bean.User">
select * from user
<if test=" order_by !=null and order_by != ''">
order by ${order_by} desc
if>
select>
<select id="findAll" resultType="org.javaboy.helloboot.bean.User">
select * from ${tb_user}
select>
4、动态 SQL 中使用了参数作为变量
如果在动态 SQL 中使用参数作为变量,那么也需要 @Param 注解,即使你只有一个参数。
例如:
@Mapper
public interface UserMapper {
List<User> getUserById(@Param("id")Integer id);
}
对应xml:
<select id="getUserById" resultType="org.javaboy.helloboot.bean.User">
select * from user
<if test="id != null">
where id=#{id}
if>
select>
连接数据库:Oracle
错误写法
<delete id="deleteEntityAll" parameterType="java.lang.reflect.Array">
<foreach collection="array" item="item" index="index" separator=";">
delete from GKCP_TC_JL where JH = #{item.jh} and to_char(TCRQ,'YYYY-MM-DD') = #{item.tcrq}
foreach>
delete>
这样写生成的SQL语句用 ; 隔开,在sql工具中可以识别,但是mybatis不识别
解决方案:在foreach中增加节点操作符可以解决(Oracle的匿名块)
正确写法:
<delete id="deleteEntityAll" parameterType="java.lang.reflect.Array">
<foreach collection="array" item="item" index="index" open="begin" close=";end;" separator=";">
delete from GKCP_TC_JL where JH = #{item.jh} and to_char(TCRQ,'YYYY-MM-DD') = #{item.tcrq}
foreach>
delete>