Mybatis框架使用详解

导读:本篇文章讲解 Mybatis框架使用详解,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com

Mybatis基础

简介

MyBatis 是支持普通 SQL 查询,存储过程和高级映射的优秀持久层框架。

MyBatis 消除了几乎所有的 JDBC 代码和参数的手工设置以及结果集的检索。

MyBatis 使用简单的 XML 或注解用于配置和原始映射,将接口和 Java 的POJOs(普通的 Java对象)映射成数据库中的记录。

每个MyBatis应用程序主要都是使用SqlSessionFactory实例的,一个SqlSessionFactory实例可以通过SqlSessionFactoryBuilder获得。SqlSessionFactoryBuilder可以从一个xml配置文件或者一个预定义的配置类的实例获得。

用xml文件构建SqlSessionFactory实例是非常简单的事情。推荐在这个配置中使用类路径资源(classpath resource),但其实可以使用任何Reader实例,包括用文件路径或 file:// 开头的 url 创建的实例。MyBatis有一个实用类—-Resources,它有很多方法,可以方便地从类路径及其它位置加载资源。

优点

  • MyBatis封装了JBDC底层访问数据库的细节,使我们程序猿不需要与JDBC API打交道,就可以访问数据库
  • MyBatis简单易学,程序猿直接编写SQL语句,适合于对SQL语句性能要求比较高的项目
  • SQL语句封装在配置文件中,便于统一管理与维护,降低了程序的耦合度
  • SQL代码从程序代码中彻底分离出来,可重用
  • 提供了动态SQL标签,支持编写动态SQL
  • 提供映射标签,支持对象与数据库的ORM字段关系映射

缺点

  • 过于依赖数据库SQL语句,导致数据库移植性差,更换数据库,如果SQL语句有差异,SQL语句工作量大
  • 由于xml里标签id必须唯一,导致DAO中方法不支持方法重载

SpringBoot集成Mybatis

依赖

SpringBoot官方并没有提供Mybatis的启动器,不过Mybatis官网自己实现了

		<!-- Mybatis启动器-->
		<dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.3</version>
        </dependency>
		<!-- mybatis分页插件 PageHelper -->
        <dependency>
            <groupId>com.github.pagehelper</groupId>
            <artifactId>pagehelper-spring-boot-starter</artifactId>
            <version>1.2.3</version>
        </dependency>       

		<!-- jdbc连接启动器-->
		<dependency>
           <groupId>org.springframework.boot</groupId>
           <artifactId>spring-boot-starter-jdbc</artifactId>
		</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");
            }
        };
    }
}

Spring集成MyBatis

依赖

        <!-- mybatis -->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>${version.mybatis}</version>
        </dependency>
		<!-- 通用Mapper -->
        <dependency>
            <groupId>tk.mybatis</groupId>
            <artifactId>mapper</artifactId>
            <version>${version.mybatis.mapper}</version>
        </dependency>
		<!-- mybatis分页插件 PageHelper -->
        <dependency>
            <groupId>com.github.pagehelper</groupId>
            <artifactId>pagehelper</artifactId>
            <version>${version.pagehelper}</version>
        </dependency>

        <!-- mybatis-spring -->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis-spring</artifactId>
            <version>${version.mybatis.spring}</version>
        </dependency>

        <!-- spring事务 -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-tx</artifactId>
        </dependency>

        <!-- spring jdbc -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
        </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

<?xml version="1.0" encoding="UTF-8"?>
<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">

    <!--datasource-->
    <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>

     <!--1、spring管理mybatis的核心对象-->
    <!--sqlSessionfactory-->
    <bean id="sqlSessionfactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource"></property>
        <property name="typeAliasesPackage" value="cn.test.domain"></property>
    </bean>

    <!--
        spring自动的对执行包下的dao接口创建动态代理对象,存入容器
            mapperScannerConfigurer:
               1、spring容器启动的时候,加载执行包下的所有接口和映射文件
               2、调用session的方法获取dao接口的动态代理对象
               3、将对象存入容器
    -->
    <bean id="mapperScannerConfigurer" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <!--dao接口所在的包-->
        <property name="basePackage" value="cn.test.dao"></property>
    </bean>


    <!--2、配置声明式事务(xml)-->
    <!-- i.事务管理器交给容器管理-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"></property>
    </bean>

    <!--ii.配置事务通知-->
    <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>
    
    <!--iii.配置事务的AOP-->
    <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>

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.test.springbootssm.mapper.UserMapper">
    <!--注意包名不要错了-->
    <select id="findById" parameterType="long" resultType="user">
        select * from tb_user where id=#{id}
    </select>
</mapper>

配置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);
    }
}

Mybatis实现Dao层

接口开发规范

  1. 接口全限定名与映射文件的namespace属性一致
  2. 接口的方法名与statement(子标签)的id属性一致
  3. 接口方法参数类型与statement(子标签)的parameterType属性一致
  4. 接口方法返回值类型与statement(子标签)的resultType属性一致

在这里插入图片描述

UserMapper接口

public interface UserMapper {
    public List<User> findAll();
}

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.test.mapper.UserMapper">

<select id="findAll" resultType="user">
	select * from user
</select>

</mapper>

Mybatis高级查询

查询结果封装

  • resultType
    如果实体的属性名与数据库表中字段名(或查询字段别名)一致,将查询结果自动封装到实体类中。

    • 如果是单条数据,mybatis直接返回封装好数据的对象
    • 如果是多条数据,mybatis会将封装好的多个对象放入list集合中
  • resutlMap
    如果实体的属性名与数据库表中字段名不一致,使用ResutlMap实现手动映射封装到实体类中

    resutlMap的属性说明 :

    <resultMap id="userResultMap" type="user">
        <!-- id 	此标签唯一标识`-->
        <!-- type 	封装后的实体类型`-->
        
    <id column="uid" property="id"></id>  						<!-- 表中主键字段封装`-->
    <result column="username" property="username"></result>  	<!-- 表中普通字段封装`-->
        <!-- column  	表中的字段名`-->
        <!-- property 	实体的属性名`-->
    

    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文件:

<!-- 方式1 -->
<select id="findByIdAndUsername3" parameterType="user" resultType="user">
    select * from user where id = #{id} and username = #{username}
</select>

<!-- 方式2(如果查询条件有多个参数  parameterType 可以省略) -->
<select id="findByIdAndUsername2" resultType="user">
    select * from user where id = #{id} and username = #{username}
</select>

<!-- 方式3(如果查询条件有多个参数  parameterType 可以省略) -->
<select id="findByIdAndUsername1" resultType="user">
    <!--  select * from user where id = #{arg0} and username = #{arg1} -->
    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文件:

<!-- 方式1 -->
<select id="findByUsername4" parameterType="string" resultType="user">
    select * from user where username like concat('%', #{username}, '%');
</select>

<!-- 方式2 -->
<select id="findByUsername4" parameterType="string" resultType="user">
	select * from user where username like ('%' || #{username} || '%');
</select>

<!-- 方式3 -->
<select id="findByUsername1" parameterType="string" resultType="user">
    select * from user where username like #{username}
</select>

<!-- 方式4 -->
<select id="findByUsername1" parameterType="string" resultType="user">
    select * from user where username like '%#{username}%'
</select>

<!-- 方式5-->
<select id="findByUsername3" parameterType="string" resultType="user">
    select * from user where username like '%${value}%'
</select>

分页查询

需要添加 PageHelper 的依赖

		<!-- mybatis分页插件 PageHelper(与SpringBoot集成版) -->
        <dependency>
            <groupId>com.github.pagehelper</groupId>
            <artifactId>pagehelper-spring-boot-starter</artifactId>
            <version>1.2.3</version>
        </dependency>
	/**
     * 分页查询所有数据
     * */
    @Override
    public PageInfo<Brand> findPage(Integer page, Integer size) {
        //调用PageHelper的startPage方法,设置当前页、每页条数
        PageHelper.startPage(page, size);
        //调用dao接口方法,查询所有的数据
//        List<Brand> 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.变量替换后,${} 对应的变量不会加上单引号 ''

必须使用@param注解的四种情况

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			<!-- order by 时,必须用 ${} 传入列名 -->
    </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>

Mybatis映射文件深入

动态sql

if 标签

if 标签:

判断语句(单条件分支)。必须结合 test 属性联合使用。

常用场景:

  • 在 WHERE 条件中使用 if 标签。根据条件判断动态拼接查询条件。
  • 在 UPDATE 更新列中使用 if 标签。只更新有变化的字段, 空值不更新。
  • 在 INSERT 动态插入中使用 if 标签。只有非空属性才插入。
  • 在 SELECT 动态查询字段使用 if 标签。根据条件动态确定查询字段。

mapper接口方法:

public List<User> findByIdAndUsernameIf(User user);

xml文件:

<select id="findByIdAndUsernameIf" parameterType="user" resultType="user">
    select * from user 
    <!-- where 1=1 是多条件拼接时的小技巧,后面的条件查询就可以都用and了 -->
    <!-- where 1 = 1 -->
    
    <!-- where 标签:相当于 where 1=1 功能,
		 当if条件都不满足时,where 将不会拼接在sql语句中
		 当if有条件满足时,where 将会拼接在sql语句中,且去掉第一个成立的 if 标签下的 and | or 等  -->
  	<where>
        <if test="id != null">
            and id = #{id}
        </if>
        <if test="username != null">
            and username like concat('%',#{username}),'%')
        </if>
    </where>
</select>

choose 标签

choose when otherwise 标签可以帮我们实现 if else 的逻辑(或 java 中的 switch 语句)。

一个 choose 标签至少有一个 when,最多一个otherwise。

mapper接口方法:

public List<User> findByIdAndUsernameChoose(User user);

xml文件:

<!--
        choose标签 相当于 switch语句
        when标签 相当于 case+break语句
        otherwise 相当于 default语句
-->
<select id="findByIdAndUsernameChoose" parameterType="user" resultType="user">
    select * from user
    <where>
        <choose>
            <when test="id != null">
                and id = #{id}
            </when>
            <when test="username != null">
                 and username like concat(concat('%',#{username}),'%')
            </when>
            <otherwise>
                and 1 = 1
            </otherwise>
        </choose>
    </where>
</select>

set 标签

可以去掉 set 标签中的最后一个条件的 逗号

mapper接口方法:

public void updateIf(User user);

xml方法:

<update id="updateIf" parameterType="user">
    update user 
    <set>
        <if test="username != null">
            username=#{username},
        </if>
        <if test="birthday != null">
            birthday=#{birthday},
        </if>
        <if test="sex != null">
            sex = #{sex},
        </if>
        <if test="address != null">
            address = #{address},
        </if>
    </set>
    where id = #{id}
</update>

foreach 标签

做数据的循环遍历

* 例如:
	select * from user where id in (1,2,3)
	在这样的语句中,传入的参数部分必须依靠 foreach遍历才能实现。

foreach 标签中的属性:

  • collection:必填, 被遍历的集合(list)/数组(array)/Map的名称
  • item:变量名。即从迭代的对象中取出的每一个值
  • index:索引的属性名。当迭代的对象为 Map 时, 该值为 Map 中的 Key.
  • open:遍历前拼接的字符串
  • close:遍历后拼接的字符串
  • separator:分隔符

where 中使用 foreach

mapper接口方法:

// foreach标签 遍历
public List<User> findByList(List<Integer> ids);

public List<User> findByArray(Integer[] ids);

// 传递引用数据类型 list属性或者array属性
public List<User> findByPojo(QueryVo queryVo);

xml文件:

<!-- 传递 普通类型list集合   collection=""   取值:collection、list -->
<select id="findByList" parameterType="list" resultType="user">
    select * from user where
    <foreach collection="list" open="id in (" close=")" item="id" separator=",">
        #{id}
    </foreach>
</select>

<!-- 传递 普通类型array数组   collection=""   取值:array -->
<select id="findByArray" parameterType="int[]" resultType="user">
    select * from user where
    <foreach collection="array" open="id in (" close=")" item="id" separator=",">
        #{id}
    </foreach>
</select>

<!-- 传递 引用数据类型list集合属性   collection="" 取值   集合或数组的 属性名 -->
<select id="findByPojo" parameterType="QueryVo" resultType="user">
    select * from user where
    <foreach collection="ids" open="id in (" close=")" item="id" separator=",">
        #{id}
    </foreach>
</select>

foreach 实现批量插入

mapper接口方法:

    // 批量插入学生
    int insertList(List<Student> students);

xml文件:

    <insert id="insertList">
        insert into student(name, phone, email, sex, locked)
        values
        <foreach collection="list" item="student" separator=",">
            (
            #{student.name}, #{student.phone},#{student.email},
            #{student.sex},#{student.locked}
            )
        </foreach>
    </insert>

trim 标签

trim 标签的属性

  • prefix: 当 trim 元素包含有内容时, 增加 prefix 所指定的前缀
  • prefixOverrides: 当 trim 元素包含有内容时, 去除 prefixOverrides 指定的前缀
  • suffix: 当 trim 元素包含有内容时, 增加 suffix 所指定的后缀
  • suffixOverrides:当 trim 元素包含有内容时, 去除 suffixOverrides 指定的后缀、

set 和 where 其实都是 trim 标签的一种类型, 该两种功能都可以使用 trim 标签进行实现。

trim 标签来实现 where 标签

当 trim 标签中含有内容时,添加 where,且第一个为 and 或 or 时,会将其去掉;而如果没有内容,则不添加 where

<trim prefix="where" prefixOverrides="AND |OR">
</trim>

trim 标签来实现 set 标签

当 trim 标签中含有内容时,添加 set,且最后的内容为 , 时,会将其去掉;而没有内容,不添加 set

<trim prefix="SET" suffixOverrides=",">
</trim>

bind 标签

bind 元素允许在 OGNL 表达式以外创建一个变量,并将其绑定到当前的上下文。

如在 selectByStudentSelective 方法中,有如下

<if test="name != null and name !=''">
      and name like concat('%', #{name}, '%')
</if>

在 MySQL 中,concat 函数支持多参数,但在 Oracle 中只支持两个参数。通过使用 bind 来让该 SQL 达到支持两个数据库的作用

<if test="name != null and name !=''">
     <bind name="nameLike" value="'%'+name+'%'"/>
     and name like #{nameLike}
</if>

include 标签(引入sql片段)

<!-- 可以将公共部分进行抽取 -->
<sql id="userSelect">
    select * from user
</sql>

<!-- 引入sql片段 -->
<select id="findByArray" parameterType="int[]" resultType="user">
    <include refid="userSelect"></include> 
    where 
    <foreach collection="array" open="id in (" close=")" item="id" separator=",">
        #{id}
    </foreach>
</select>

<select id="findByPojo" parameterType="QueryVo" resultType="user">
    <include refid="userSelect"></include> 
    where 
    <foreach collection="ids" open="id in (" close=")" item="id" separator=",">
        #{id}
    </foreach>
</select>

生成并返回主键

往数据库中插入记录时,生成主键并将主键返回到原实体对象中

  • 方式1:<selectKey>【复杂,但通用】

    既支持主键自增类型,也支持 UUID 类型的主键的生成与返回

  • 方式2:useGeneratedKeys【简单,但不通用】

    注意:这种方式仅支持主键自增类型的数据库,如 MySQL 和 sqlServer,oracle不支持

xml文件:

<!--
	方式1:<selectKey>
		keyColumn    	数据库表的主键字段
		keyProperty		指定实体的主键属性名。selectKey语句结果应该被设置的目标属性
		resultType		指定实体主键的类型。
						可以不写,Mybatis通常可以推算出来。Mybatis允许任何简单类型作为主键的类型,包括字符串。
		order="AFTER"  selectKey的sql语句是在insert执行之前(执行),还是之后(执行)
			AFTER 	之后执行。适合返回自增id的情况(主键是自增列,插入之后才能获知)。SELECT LAST_INSERT_ID() FROM DUAL
			BEFORE 	之前执行。适合返回UUID的情况(主键不是自增列,需要预先生成)。SELECT UUID() FROM DUAL
    -->
<insert id="save2" parameterType="user">
    <selectKey keyColumn="id" keyProperty="id" resultType="int" order="AFTER">
        SELECT LAST_INSERT_ID() FROM DUAL
    </selectKey>
    insert into user(username, birthday, sex, address) values(#{username}, #{birthday}, #{sex}, #{address})
</insert>

<!--
	方式2:useGeneratedKeys
        useGeneratedKeys="true" 开启返回主键功能
		keyColumn    			数据库表的主键字段
		keyProperty  			指定实体的主键属性名
	
    -->
<insert id="save1" parameterType="user" useGeneratedKeys="true" keyColumn="id" keyProperty="id">
    insert into user(username,birthday,sex,address) values(#{username},#{birthday},#{sex},#{address})
</insert>

多表查询

表关系及xml标签配置介绍

一对一   	订单与用户。查询站在订单角度:一个订单只能属于一个用户
一对多   	用户和订单。查询站在用户角度:一个用户可以拥有多个订单
多对一		订单和用户。多个订单,可以被一个用户拥有
多对多   	用户和角色。	- 站在用户角度:一个用户可以拥有多个角色
					  - 站在角色角度:一个角色可以被多个用户拥有


mybatis:一个表是一个实体的映射(user  orders)
实体: 实体属性、集合属性
1 v 1: 实体属性
1 v N: 1的一方有多的一方的集合属性  多的一方有1的一方的实体对象


* 一对一配置:使用<resultMap>+<association>做配置
	association:
    	property:关联的实体属性名
    	javaType:关联的实体类型(使用别名)

* 一对多配置:使用<resultMap>+<collection>做配置
	collection:
		property:关联的集合属性名
		ofType:关联的集合泛型类型(使用别名)

* 多对多配置:使用<resultMap>+<collection>做配置
	collection:
		property:关联的集合属性名
		ofType:关联的集合泛型类型(别名)
		
* 多对多的配置跟一对多很相似,区别在于SQL语句的编写。

多表查询和嵌套查询的实体类

Orders(订单)实体类

@Data
public class Orders {
    private Integer id;
    private Date ordertime;
    private Double money;
    // 订单所属的用户
    private User user;
}

User(用户)实体类

@Data
public class User {
    private Integer id;
    private String username;
    private Date birthday;
    private String sex;
    private String address;
    
    // 一个用户有多个角色
    private List<Role> roleList;

    // 一个用户有多个订单
    private List<Orders> orders;
}

Role(角色)实体类

@Data
public class Role {
    private Integer id;
    private String roleName;
    private String roleDesc;
}

一对一(多对一)

一对一查询的实例:

需求:查询一个订单,与此同时查询出该订单所属的用户(1 v 1)

OrdersMapper.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="cn.test.dao.OrdersDao">

    <!--手动封装-->
    <resultMap id="findOrderswithUserResultMap" type="orders">
            <!--订单orders-->
            <id column="iid" property="id"></id>
            <result column="ordertime" property="ordertime"></result>
            <result column="money" property="money"></result>
            <!--订单中的用户User(一对一的封装)
                    association标签
                    	property:订单实体类中的用户属性名
                        javaType:要封装数据的属性属于什么类型。不写则自动映射
            -->
            <association property="user" javaType="cn.test.domain.User">
                    <id column="uid" property="id"></id>
                    <result column="username" property="username"></result>
                    <result column="birthday" property="birthday"></result>
                    <result column="sex" property="sex"></result>
                    <result column="address" property="address"></result>
            </association>
    </resultMap>
    
    <!-- 查询当前订单以及所属用户的信息 -->
    <select id="findOrderswithUser" parameterType="int" resultMap="findOrderswithUserResultMap">
        select *, o.id iid from orders o left join user u on o.uid=u.id where o.id=#{id}
    </select>
</mapper>

一对多

一对多查询的实例:

需求:查询一个用户,与此同时查询出该用户具有的所有订单(1 v N)

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="cn.test.dao.UserDao">

    <!--手动封装-->
    <resultMap id="findUserwithOrdersResultMap" type="user">
        <!--封装user数据-->
        <id column="id" property="id"></id>
        <result column="username" property="username"></result>
        <result column="birthday" property="birthday"></result>
        <result column="sex" property="sex"></result>
        <result column="address" property="address"></result>
        <!--封装user的list集合属性(一对多)
            	collection标签
                	property:用户的订单集合属性名
                    ofType:要封装的list集合中的泛型类型
        -->
        <collection property="list" ofType="orders">
            <id column="iid" property="id"></id>
            <result column="ordertime" property="ordertime"></result>
            <result column="money" property="money"></result>
        </collection>
    </resultMap>
    
    <!-- 查询一个用户及订单的信息 -->
    <select id="findUserwithOrders" parameterType="int" resultMap="findUserwithOrdersResultMap">
        select *, o.id as iid from user u left join orders o on u.id=o.uid where u.id=#{id}
    </select>
</mapper>

多对多(二个一对多)

多对多的查询的实例:

需求: 查询某个用户的所有角色或者是某个角色的所有用户

  • 站在用户角度,一个用户可以拥有多个角色
  • 站在角色角度,一个角色可以被多个用户拥有
  • 所以 mybatis框架多对多的实现,跟一对多的步骤是一样的,区别点在于sql语句

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="cn.test.dao.UserDao">
    
	<resultMap id="findUserwithRoleResult" type="user">
            <!--用户数据-->
            <id column="id" property="id"></id>
            <result column="username" property="username"></result>
            <result column="birthday" property="birthday"></result>
            <result column="sex" property="sex"></result>
            <result column="address" property="address"></result>
            <!--用户的角色集合(一对多)
                	collection标签
                    	property:用户的角色集合属性名
                        oftype:要封装的类型
            -->
            <collection property="roleList"  ofType="Role">
                    <id column="rid" property="id"></id>
                    <result column="role_name" property="roleName"></result>
                   <result column="role_desc" property="roleDesc"></result>
            </collection>
    </resultMap>
    
    <!-- 查询某个用户及角色信息 -->
    <select id="findUserwithRole" parameterType="int" resultMap="findUserwithRoleResult">
        select * from user u 
        left join user_role ur on u.id=ur.uid  
        left join role r on r.id=ur.rid 
        where u.id=#{id}
    </select>
</mapper>

嵌套查询(了解)

Mybatis嵌套查询 介绍:将多表查询的复杂sql语句拆分多个单表查询,再由mybatis框架进行嵌套组合

  • 优点:减少sql复杂性
  • 缺点:需要编写多次sql和多个配置,代码麻烦。故一般不推荐使用

简单示例:

* 需求:查询一个订单,与此同时查询出该订单所属的用户  1v1

# 关联查询
select * from orders o left join user u on o.uid=u.id where o.id=1;

# 嵌套查询 将一条sql语句能完成的事情拆分成多条单表查询去完成
#1 查询订单号为1的订单
#2 在根据查询的订单的uid去查用户表
#3 mybatis进行组装(映射文件中组装)因最终要返回的是订单 所以去订单映射文件中组装

一对一(多对一)

需求:查询一个订单,与此同时查询出该订单所属的用户

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="cn.test.mapper.OrdersMapper">
   
    <resultMap id="ordersResultMap" type="orders">
        <id column="id" property="id"></id>
        <result column="ordertime" property="ordertime"></result>
        <result column="address" property="address"></result>
        <!--mybatis框架嵌套查询(一对一映射)
                association标签
                    property:要封装的对象属性名
                    select:数据来源的方法名
                    column:方法需要的参数(uid)
        -->
        <association property="user" select="cn.test.mapper.UserMapper.findUserById" 
                     column="uid">
        </association>
    </resultMap>
    
    <!-- 查询当前订单 -->
    <select id="findOrderById" parameterType="int" resultMap="ordersResultMap">
        select * from orders where id=#{id}
    </select>
</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">
<mapper namespace="cn.test.mapper.UserMapper">
    <!-- 查询当前用户 -->
    <select id="findUserByUId" parameterType="int" resultType="user">
        select * from user where id=#{id}
    </select>
</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="cn.test.mapper.UserMapper">
    
    <resultMap id="userResultMap" type="user">
        <id column="id" property="id"></id>
        <result column="username" property="username"></result>
        <result column="birthday" property="birthday"></result>
        <result column="sex" property="sex"></result>
        <result column="address" property="address"></result>
        <!-- 嵌套查询(一对多的映射)
                collection标签
                    property;要封装的对象集合属性
                    ofType:泛型类型
				    select:数据的来源方法	
				    column:方法需要的参数数据(用户的id)	
        -->
        <collection property="orders" ofType="Orders" select="cn.test.mapper.OrdersMapper.findBylist" 
                    column="id">
        </collection>

    <!-- 查询当前用户 -->
    </resultMap>
    <select id="findUserById" parameterType="int" resultMap="userResultMap">
        select * from user where id=#{id}
    </select>
</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">
<mapper namespace="cn.test.mapper.OrdersMapper">
   
    <!--查询多个订单-->
    <select id="findBylist" parameterType="int" resultType="Orders">
        select * from orders where uid=#{id}
    </select>
</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="cn.test.mapper.UserMapper">

    <resultMap id="userResultMap" type="user">
        <id column="id" property="id"></id>
        <result column="username" property="username"></result>
        <result column="birthday" property="birthday"></result>
        <result column="sex" property="sex"></result>
        <result column="address" property="address"></result>
        <!-- 嵌套查询(一对多的映射)
                collection标签
                    property;要封装的对象集合属性
                    ofType:泛型类型
                    select:数据的来源方法	
				   column:方法需要的参数数据(用户的id)

        -->
        <collection property="roleList" ofType="role" select="cn.test.mapper.RoleMapper.findRoles" 
                    column="id">
        </collection>
    </resultMap>
    
    <!--查询当前用户-->
    <select id="findUserById" parameterType="int" resultMap="userResultMap">
        select * from user where id=#{id}
    </select>
</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">
<mapper namespace="cn.test.mapper.RoleMapper">
    
    <resultMap id="RoleResultMap" type="role">
        <id column="id" property="id"></id>
        <result column="role_name" property="roleName"></result>
        <result column="role_desc" property="roleDesc"></result>
    </resultMap>
    
    <!--查询当前用户的角色-->
    <select id="findRoles" resultMap="RoleResultMap" parameterType="int">
        SELECT * FROM user_role ur INNER JOIN role r ON ur.rid=r.id WHERE ur.uid=#{id}
    </select>
</mapper>

调用存储过程

参考:https://blog.csdn.net/dwenxue/article/details/82257944

在mapper文件中可以使用statementType标记使用什么的对象操作SQL语句。

statementType:标记操作SQL的对象

取值说明:

  • STATEMENT:直接操作sql,不进行预编译,获取数据。$—Statement
  • PREPARED:预处理,参数,进行预编译,获取数据。#—PreparedStatement。默认
  • CALLABLE:执行存储过程。CallableStatement

示例:

  1. 创建insert_user存储过程(MySql)

    CREATE PROCEDURE insert_user(OUT u_id INTEGER,IN u_name VARCHAR(20),IN u_sex VARCHAR(20),IN u_age INTEGER)
    BEGIN
    INSERT INTO t_user (name,sex,age) VALUES (u_name,u_sex,u_age);
    SET u_id=LAST_INSERT_ID();
    END
    

    在UserMapper.xml中调用insert_user存储过程

    <!-- 添加用户 -->
    <insert id="addUser" parameterType="com.po.User" statementType="CALLABLE">
        {call insert_user(#{id,mode=OUT,jdbcType=INTEGER},#{name,mode=IN},#{sex,mode=IN},#{age,mode=IN})}
    </insert>
    
  2. 创建deleteUser存储过程(MySql)

    CREATE PROCEDURE deleteUser(IN u_id INTEGER)
    BEGIN
       DELETE FROM t_user WHERE id=u_id;
    END
    

    在UserMapper.xml中调用deleteUser存储过程

    <!-- 删除用户 -->
    <delete id="deleteUser" parameterType="Integer" statementType="CALLABLE">
        {call deleteUser(#{id,mode=IN})}
    </delete>
    
  3. 创建updateUser存储过程(MySql)

    CREATE PROCEDURE updateUser(IN u_id INTEGER,IN u_name VARCHAR(20),IN u_sex VARCHAR(20),IN u_age INTEGER)
    BEGIN
        UPDATE t_user SET name=u_name,sex=u_sex,age=u_age WHERE id=u_id;
    END
    

    在UserMapper.xml中调用updateUser存储过程

    <!-- 更新用户 -->
    <update id="updateUser" parameterType="user" statementType="CALLABLE">
        {call updateUser(#{id,mode=IN},#{name,mode=IN},#{sex,mode=IN},#{age,mode=IN})}
    </update>
    
  4. 创建getUserById存储过程(MySql)

    CREATE PROCEDURE getUserById(IN u_id INTEGER)
    BEGIN
        SELECT id,name,sex,age FROM t_user WHERE id=u_id;
    END
    

    在UserMapper.xml中调用getUserById存储过程

    <!-- 根据id查询用户 -->
    <select id="getUserById" parameterType="Integer" resultType="user" statementType="CALLABLE">
        {call getUserById(#{id,mode=IN})}
    </select>
    

通用mapper

官方网站:https://mybatis.io/

github源码:https://github.com/abel533/Mapper

gitee源码:https://gitee.com/free/Mapper/wikis/Home

简介、依赖及常用配置

通用Mapper是简化mybatis操作的一个框架,是为了解决单表增删改查,基于Mybatis的插件。开发人员不需要建立 xml 映射文件编写 SQL,不需要在dao接口中增加方法,只需要写好实体类,用注解跟数据库的表和字段建立映射关系,然后在dao接口继承BaseMapper<T>并指定泛型,就能支持相应的增删改查方法。在service实现类中,直接调用相关方法,就可以执行简单的单表CRUD。

SpringBoot官方并没有提供通用Mapper的启动器,不过通用Mapper的作者为自己的插件编写了启动器

注意:一旦引入了通用Mapper的启动器,会覆盖Mybatis官方启动器的功能,因此需要移除对官方Mybatis启动器的依赖。

(把mybatis相关的配置文件删除、把引导类上mapperScan注解删除、把mybatis的启动器删除)

        <!-- 通用Mapper启动器(与SpringBoot集成版) -->
        <dependency>
            <groupId>tk.mybatis</groupId>
            <artifactId>mapper-spring-boot-starter</artifactId>
            <version>2.1.5</version>
        </dependency>

启动类上若有@MapperScan注解,则需修改为通用Mapper中自带的:

import tk.mybatis.spring.annotation.MapperScan;

修改mapper接口

public interface UserMapper extends BaseMapper<User> {
}

在实体类上添加注解@Table,主键上添加@Id

@Data
@Table(name = "tb_user")	// 指定要映射的数据库中的哪个表
public class User {
    /** id */
    @Id		//表示该成员变量是主键id
    private Long id;
    /** 用户名 */
    private String userName;
    /** 密码 */
    private String password;
}

常用API

**根据主键id查询:**selectByPrimaryKey(id);

添加数据:

  • insertSelective(brand):添加数据,如果添加的数据中有为null的字段,则不进行更改。推荐,如果有特需,则用下面方式

  • insert(brand):添加数据,不管要添加的数据是否为null,都进行更改,如果为null则将数据库中的该字段修改为null

根据id更新数据:

  • updateSelective(brand):更新数据,如果添加的数据中有为null的字段,则不进行更改。推荐,如果有特需,则用下面方式

  • update(brand):更新数据,不管要添加的数据是否为null,都进行更改,如果为null则将数据库中的该字段修改为null

    注意:根据id更新数据,那么传入的 pojo 对象中的 id 字段一定要有值,否则它不知道修改哪条数据

**根据id删除数据:**deleteByPrimaryKey(id);

拓展了解

Mybatis核心文件概述

在这里插入图片描述

1)environments标签

数据库环境配置

在这里插入图片描述

  1. 其中,事务管理器(transactionManager)类型有两种:
  • JDBC:
    这个配置就是直接使用了JDBC 的提交和回滚设置,由mybatis自己手动控制事务
  • MANAGED:
    这个配置几乎没做什么。它从来不提交或回滚一个连接,而是让容器来管理事务的整个生命周期。
    例如:mybatis与spring整合后,事务交给spring容器管理。
  1. 其中,数据源(dataSource)常用类型有二种:
  • UNPOOLED:
    这个数据源的实现只是每次被请求时打开和关闭连接。
  • POOLED:
    这种数据源的实现利用“池”的概念将 JDBC 连接对象组织起来。使用的是mybatis自带的

2)properties标签

第三方属性配置

实际开发中,习惯将数据源的配置信息单独抽取成一个properties文件

	 <properties resource="jdbc.properties"></properties>	
	 
	 <environments default="mysql">
        <!--mysql数据库环境-->
        <environment id="mysql">
            <!-- 使用JDBC类型事务管理器 -->
            <transactionManager type="JDBC"></transactionManager>
            <!-- 数据源(连接池)配置  POOLED-->
            <dataSource type="POOLED">
                <property name="driver" value="${jdbc.driver}"></property>
                <property name="url" value="${jdbc.url}"></property>
                <property name="username" value="${jdbc.username}"></property>
                <property name="password" value="${jdbc.password}"></property>
            </dataSource>
        </environment>
    </environments>

3)typeAliases标签

实体类型别名配置

	<typeAliases>
        <!--指定一个实体类和对应的别名-->
        <!--<typeAlias type="com.test.domain.User" alias="user"></typeAlias>-->
        
        <!--指定当前包下所有的实体设置别名  默认: 别名(类名) -->
        <package name="com.test.domain"></package>
    </typeAliases>

在这里插入图片描述

4)mappers标签

映射关系配置(加载映射配置文件)

	<!-- 加载指定的src目录下的映射文件 -->
	<mapper resource="org/mybatis/builder/AuthorMapper.xml"/>

核心配置文件标签顺序

在这里插入图片描述

Mybatis的API概述

1)Resources

专门用于加载mybatis核心配置文件,返回的 io流

InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml");

2)SqlSessionFactoryBuilder

专门用于生产SqlSessionFactory工厂对象

SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(in);

3)SqlSessionFactory

一个项目只有一个工厂对象,生产sqlSession对象

// 需要手动提交事务,DML语句才会持久化到数据库中
SqlSession openSession();	// 【推荐】
	
// 设置是否开启自动提交,如果设置为true,开启自动提交事务
SqlSession openSession(boolean autoCommit);
	参数说明
		true:每一条DML类型语句都会自动提交事务
		false(默认值):需要手动提交事务

4)SqlSession

是mybatis核心对象,可以对数据库进行CRUD基本操作

//  方法
<T> T selectOne(String statement, Object parameter);
<E> List<E> selectList(String statement, Object parameter);
int insert(String statement, Object parameter);
int update(String statement, Object parameter);
int delete(String statement, Object parameter);
    
// 事务
void commit();
void rollback();

MyBatis延迟加载(懒加载)

Mybatis的延迟加载是针对嵌套查询而言的,是指在进行查询的时候先只查询最外层的SQL,对于内层SQL将在需要使用的时候才查询出来。

使用延迟加载的场景:一对多、多对多

不推荐使用延迟加载的场景:一对一(使用 立即加载)

特别注意:延迟加载是基于 嵌套查询 组合使用

1)局部延迟加载

* 需要手动在每一个select相关标签中单独配置
	<association>、<collection>
		fetchType="lazy"  懒加载
		fetchType="eager" 立即加载

2)全局延迟加载

lazyLoadingEnabled:延迟加载的全局开关。当开启时,所有关联对象都会延迟加载,特定关联关系中可通过设置 fetchType 属性来覆盖该项的开关状态。默认值为false。

	<!-- 在核心配置文件SqlMapConfig.xml,设置全局参数 -->
    <settings>
        <!--开启懒加载-->
        <setting name="lazyLoadingEnabled" value="true"/>
    </settings>

若是pringboot集成,在yaml配置文件中配置开启延迟加载。

mybatis:
  configuration:
    lazy-loading-enabled: true 		#开启延时加载开关

注意:如果全局配置了延迟加载,那么一对一也会起作用

可以为一对一设置局部的立即加载,因为局部优先级高于全局延迟加载

<association property="user" javaType="User" column="uid" select="com.itheima.mapper.UserMapper.findById" 
             fetchType="eager"> 
</association>

3)指定触发延迟加载的方法配置

lazyLoadTriggerMethods 默认情况下仅仅支持自动将 equals,clone,hashCode,toString 这几个方法定义为延迟加载的加载触发方法。

* 在SqlMapConfig.xml核心配置文件,设置全局参数
	<settings>
		<!-- 如果将 Person 的 doLazyLoadingNow()方法加入这个列表中,
			 则调用 doLazyLoadingNow()方法将会导致 Person 上的所有延迟加载属性的关联对象被执行加载。 -->
		<setting name="lazyLoadTriggerMethods" value="doLazyLoadingNow,equals,clone,hashCode,toString"/>
	</settings>

若是springboot集成,在yaml配置文件中配置

mybatis:
  configuration:
    lazy-loading-enabled: true 		# 开启延时加载开关
    aggressive-lazy-loading: false 	# 将积极加载改为消极加载(即按需加载),默认值就是false
    # 指定触发延迟加载的方法
    lazy-load-trigger-methods: "doLazyLoadingNow,equals,clone,hashCode,toString"

MyBatis缓存

参考:https://blog.csdn.net/jinhaijing/article/details/84230810

缓存是服务器内存的一块区域。经常访问但又不会时时发生变化的数据适合使用缓存。

mybatis也支持缓存:提高查询速度,减轻数据库访问压力。

一级缓存(本地缓存)

MyBatis自带一级缓存且不可卸载

当执行查询以后,查询的结果会同时缓存到SqlSession为我们提供的一块区域中,该区域的结构是一个Map,当再次查询同样的数据时,mybatis会先去sqlsession中查询缓存,有的话直接拿出来用。当SqlSession对象消失时,mybatis的一级缓存也就消失了,同时一级缓存是SqlSession范围的缓存,当调用SqlSession的修改、添加、删除、commit(),close等方法时,就会清空一级缓存。

特点:随着sqlSession的创建而存在,随着SqlSession的销毁而销毁

简称:sqlSession级别的缓存

一级缓存失效的四种情况:

  • sqlSession不同(会话不同)

  • sqlSession相同,查询缓存中没有的数据

  • sqlSession相同,但两次查询之间执行了增删改操作

    说明:为了防止增删改对当前数据的影响,即使查的同一个对象,也会重新查数据库

    原因:每个增删改标签都有默认刷新缓存配置:flushCache=“true”(刷新一级和二级缓存)

  • sqlSession相同,但手动清楚了一级缓存(缓存清空)

    手动清空一级缓存:openSession.clearCache();

没有使用到当前一级缓存的情况,就会再次向数据库发出查询

二级缓存(全局缓存)

基于 mapper.xml 中 namespace 命名空间级别的缓存,即同一个 namespace 对应一个二级缓存。两个mapper.xml的namespace如果相同,则这两个mapper执行sql查询到数据将存在相同的二级缓存区域中。

也称为SqlSessionFactory级别的缓存,由同一个SqlSessionFactory对象创建的多个SqlSession共享其缓存,但是其中缓存的是数据而不是对象,所以从二级缓存再次查询出得结果的对象与第一次存入的对象是不一样的。

注意:不是程序自带的,需要配置。仅做了解,一般不推荐使用(一般使用Redis缓存)。

配置流程:

  1. 在核心配置文件 SqlMapConfig.xml 中开启二级缓存(可以省略)

    <settings>
        <!--开启对二级缓存的支持 默认是支持的可以不用配置-->
        <setting name="cacheEnabled" value="true"/>
    </settings>
    

    若是springboot集成,在yaml配置文件中配置开启二级缓存(可以省略)

    mybatis:
      configuration:
        cache-enabled: true #打开全局缓存开关(二级环境),默认值就是true
    
  2. 在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">
    <mapper namespace="com.atguigu.mybatis.dao.EmployeeMapper">
    	
        <!-- 声明使用二级缓存 -->
    	<cache><cache/>
    <!--	<cache eviction="FIFO" flushInterval="60000" readOnly="false" size="1024"></cache>  -->
     
     	<select id="getEmpByLastNameLikeReturnMap" resultType="com.atguigu.mybatis.bean.Employee">
     		select * from tbl_employee where last_name like #{lastName}
     	</select>
    </mapper>
    

    <cache>中可以配置一些参数:

    • eviction:缓存的回收策略:

      • LRU – 最近最少使用的:移除最长时间不被使用的对象。默认
      • FIFO – 先进先出:按对象进入缓存的顺序来移除它们。
      • SOFT – 软引用:移除基于垃圾回收器状态和软引用规则的对象。
      • WEAK – 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。
    • flushInterval:缓存刷新间隔。缓存多长时间清空一次,默认不清空,可以设置一个毫秒值

    • readOnly:是否只读:

      • true:只读;mybatis认为所有从缓存中获取数据的操作都是只读操作,不会修改数据。

        mybatis为了加快获取速度,直接就会将数据在缓存中的引用交给用户。不安全,速度快

      • false:非只读:mybatis觉得获取的数据可能会被修改。

        mybatis会利用序列化&反序列的技术克隆一份新的数据给你。安全,速度慢

    • size:缓存存放多少元素

    • type=””:指定自定义缓存的全类名

  3. 实体类对象需要实现序列化接口

    public class User implements Serializable
    
  4. 操作的过程中需要提交之后才会存入到二级缓存

    查询数据提交到二级缓存中:sqlsession.commit | sqlsession.close

    **注意:**如果 openSession.close(); 在第二次查询之后才关闭,则第二次查询会从一级缓存中查,如果不是一个session,则查询不到数据,然后就会发送sql去查数据库;

    @Test
    public void testSecondLevelCache() throws IOException{
    	SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
    	SqlSession openSession = sqlSessionFactory.openSession();
    	SqlSession openSession2 = sqlSessionFactory.openSession();
    	try{
    		EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
    		EmployeeMapper mapper2 = openSession2.getMapper(EmployeeMapper.class);
    		
    		Employee emp01 = mapper.getEmpById(1);
    		System.out.println(emp01);
    		
    		Employee emp02 = mapper2.getEmpById(1);
    		System.out.println(emp02);
    		openSession.close();
    		openSession2.close();
    	}
    }
    

工作机制:

  1. 一个会话,查询一条数据,这个数据会默认先被放在当前会话的一级缓存中;

  2. 当会话关闭或者提交后,一级缓存中的数据会被保存到二级缓存中;

    然后当有新的会话查询数据,若是同一个 namespace 就可以获取二级缓存中的内容。

    sqlSession ==> EmployeeMapper ==> Employee

    • 不同namespace查出的数据会放在自己对应的缓存中(map)
    • 效果:数据会从二级缓存中获取

缓存有关的设置/属性

  1. mybatis全局配置文件中配置全局缓存开启和清空

    1)控制二级缓存的开启和关闭

    	<setting name="cacheEnabled" value="true"/>
    	<!-- cacheEnabled=true:开启缓存;false:关闭缓存(二级缓存关闭)(一级缓存一直可用的) -->
    

    2)控制一级缓存的开启和关闭

    	<setting name="localCacheScope" value="SESSION"/>
    	<!-- localCacheScope:本地缓存作用域(一级缓存SESSION);
         	 当前会话的所有数据保存在会话缓存中;STATEMENT:可以禁用一级缓存 -->
    

    注意:一级缓存关闭后,二级缓存自然也无法使用;

  2. mapper.xml 中也可以配置一级和二级缓存开启和使用

    1)每个select标签都默认配置了useCache=“true”,表示会将本条语句的结果进行二级缓存。

    ​ 如果useCache= false:则表示不使用缓存(一级缓存依然使用,二级缓存不使用)

    2)每个 增删改 标签都默认配置了 flushCache=“true”,表示增删改执行完成后就会刷新一级二级缓存。

    ​ 注意:查询标签 <select> 默认 flushCache=“false”:如果 flushCache=true; 则每次查询之后都会清空缓存;一级和二级缓存都无法使用。

多数据库支持

如果配置了 DatabaseIdProvider,可以在动态代码中使用名为 “_databaseId” 的变量来为不同的数据库构建特定的语句。

示例:

<insert id="insert">
    <selectKey keyProperty="id" resultType="int" order="BEFORE">
        <if test="_databaseId == 'oracle'">
            select seq_users.nextval from dual
        </if>
        <if test="_databaseId == 'db2'">
            select nextval for seq_users from sysibm.sysdummy1"
        </if>
    </selectKey>
    insert into users values (#{id}, #{name})
</insert>

动态 SQL 中的插入脚本语言

MyBatis 从 3.2 版本开始支持插入脚本语言,这允许插入一种语言驱动,并基于这种语言来编写动态 SQL 查询语句。

可以通过实现以下接口来插入一种语言:

public interface LanguageDriver {
  ParameterHandler createParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql);
  SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType);
  SqlSource createSqlSource(Configuration configuration, String script, Class<?> parameterType);
}

实现自定义语言驱动后,就可以在 mybatis-config.xml 文件中将它设置为默认语言:

<typeAliases>
  <typeAlias type="org.sample.MyLanguageDriver" alias="myLanguage"/>
</typeAliases>
<settings>
  <setting name="defaultScriptingLanguage" value="myLanguage"/>
</settings>

或者,也可以使用 lang 属性为特定的语句指定语言:

<select id="selectBlog" lang="myLanguage">
  SELECT * FROM BLOG
</select>

或者,在 mapper 接口上添加 @Lang 注解:

public interface Mapper {
  @Lang(MyLanguageDriver.class)
  @Select("SELECT * FROM BLOG")
  List<Blog> selectBlog();
}

MyBatis注解开发

单表注解

* @Insert:实现新增,代替了<insert></insert>

* @Update:实现更新,代替了<update></update>

* @Delete:实现删除,代替了<delete></delete>

* @Select:实现查询,代替了<select></select>

* @Result:实现结果集封装,代替了<result></result>

* @Results:可以与@Result 一起使用,封装多个结果集,代替了<resultMap></resultMap>

注解对用户表实现增删改查操作

import cn.test.doman.User;
import org.apache.ibatis.annotations.*;

import java.util.List;

// 注解的单表crud操作
@Mapper
public interface UserMapper {

    //1 根据id做查询
    @Select("select * from user where id =#{id}")
    public User findById(int id);


    //2 全查
    @Select("SELECT id AS iid,username AS name,birthday AS bir,sex AS se,address AS addr FROM USER")
    @Results({
            /*column:字段
            * property:对象属性
            * id:默认是false 代表当前不是主键
            * */
            @Result(column = "iid",property = "id",id=true),
            @Result(column = "name",property = "username"),
            @Result(column = "bir",property = "birthday"),
            @Result(column = "se",property = "sex"),
            @Result(column = "addr",property = "address")
    })
    public List<User> findAll();


    //3 新增
    @Insert("insert into user(username,birthday,sex,address) "
            + "values(#{username},#{birthday},#{sex},#{address})")
    public void save(User user);
    
    //4 修改
    @Update("update user set username=#{username},birthday=#{birthday},sex=#{sex},address=#{address} "
            + "where id=#{id}")
    public void update(User user);
    
    //5 删除
    @Delete("delete from user where id=#{id}")
    public void delete(int id);
}

多表注解

注解开发的多表查询是嵌套查询方式

@Results	代替的是标签<resultMap>。该注解中可以使用单个@Result注解,也可以使用@Result集合。
			使用格式:@Results({@Result(), @Result()})或@Results(@Result())
			
@Result		代替<id>标签和<result>标签
			@Result中属性介绍:
				column:数据库的列名
				property:需要装配的属性名
				one:需要使用@One注解(@Result(one=@One ()))
				many:需要使用@Many注解(@Result(many=@Many ()))
				
@One(一对一)	代替了<assocation>标签,是多表查询的关键,在注解中用来指定子查询返回单一对象。
				@One注解属性介绍:
					select:指定用来多表查询的 sqlmapper
				使用格式:@Result(column=" ", property=" ", one=@One(select=""))
				
@Many(一对多)	代替了<collection>标签,是多表查询的关键,在注解中用来指定子查询返回对象集合。
				使用格式:@Result(column=" ", property=" ", many=@Many(select=""))

1)一对一

import org.apache.ibatis.annotations.One;
import org.apache.ibatis.annotations.Result;
import org.apache.ibatis.annotations.Results;
import org.apache.ibatis.annotations.Select;

@Mapper
public interface OrdersMapper {

    // 查询订单
    @Select("select * from orders where id=#{id}")
    @Results({
            @Result(column = "id",property = "id",id = true),
            @Result(column = "ordertime",property = "ordertime"),
            @Result(column = "money",property = "money"),
            /* 组装用户
            *    一对一 one=@one
            *    select:代表要封装的数据方法来源
            *    column:方法需要的参数
            *    property:要封装的对象属性名
            *    javaType:对象属性名的字节码(.class)类型
            * */
            @Result(one=@One(select = "cn.test.mapper.UserMapper.findById")
                    ,property = "user",javaType = User.class, column = "uid")
    })
    public Orders findOrders(int id);
}
@Mapper
public interface UserMapper {

    //1 根据id做查询
    @Select("select * from user where id =#{id}")
    public User findById(int id);
}

2)一对多

@Mapper
public interface UserMapper {

    //1 根据id做查询
    @Select("select * from user where id =#{id}")
    @Results({
            @Result(column = "id",property = "id",id=true),
            @Result(column = "username",property = "username"),
            @Result(column = "birthday",property = "birthday"),
            @Result(column = "sex",property = "sex"),
            @Result(column = "address",property = "address"),
            /*组装订单orders
            *     一对多
            *     many=@many
             *     select:代表要封装的数据方法来源
             *     column:方法需要的参数
             *     property:要封装的对象属性名
             *     javaType:对象属性名的字节码(.class)类型
            * */
            @Result(many = @Many(select = "cn.test.mapper.OrdersMapper.findOrdersList"),
                    property = "ordersList",javaType =List.class,column = "id")
    })
    public User findById(int id);
}    
@Mapper
public interface OrdersMapper {
	/*根据用户id获取订单集合*/
    @Select("select * from orders where uid=#{用户的id}")
    public List<Orders> findOrdersList(int id);
}

3)多对多

@Mapper
public interface UserMapper {

    //1 根据id做查询
    @Select("select * from user where id =#{id}")
    @Results({
            @Result(column = "id",property = "id",id=true),
            @Result(column = "username",property = "username"),
            @Result(column = "birthday",property = "birthday"),
            @Result(column = "sex",property = "sex"),
            @Result(column = "address",property = "address"),
            // 组装订单orders
            @Result(many = @Many(select = "cn.test.mapper.OrdersMapper.findOrdersList"
                                 , fetchType = FetchType.LAZY)
                    , property = "ordersList",javaType =List.class,column = "id"),
			// 组装角色
            @Result(many = @Many(select = "cn.test.mapper.RoleMapper.findRoles")
                    ,property = "roleList",javaType = List.class,column = "id")
    })
    public User findById(int id);
}       
@Mapper
public interface RoleMapper {

    /*查询指定用户的角色*/
    @Select("SELECT * FROM user_role ur INNER JOIN role r ON ur.rid=r.id WHERE ur.uid=#{id}")
    @Results({
            @Result(column = "id",property = "id",id=true),
            @Result(column = "role_name",property = "roleName"),
            @Result(column = "role_desc",property = "roleDesc")
    })
    public List<Role> findRoles(int id);
}

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/112030.html

(0)
Java光头强的头像Java光头强

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!