当前位置: 移动技术网 > IT编程>开发语言>Java > MyBatis知识概括

MyBatis知识概括

2020年07月26日  | 移动技术网IT编程  | 我要评论

MyBatis介绍

  • MyBatis 是支持定制化 SQL、存储过程以及高级 映射的优秀的持久层框架。
  • MyBatis 避免了几乎所有的 JDBC 代码和手动设 置参数以及获取结果集。
  • MyBatis可以使用简单的XML或注解用于配置和原始映射,将接口和Java的POJO(Plain Old Java
    Objects,普通的Java对象)映射成数据库中的记录.

为什么要使用MyBatis?

  1. MyBatis是一个半自动化的持久化层框架。
  2. JDBC
    SQL夹在Java代码块里,耦合度高导致硬编码内伤
    维护不易且实际开发需求中sql是有变化,频繁修改的情况多见
  3. Hibernate和JPA
    长难复杂SQL,对于Hibernate而言处理也不容易
    内部自动生产的SQL,不容易做特殊优化。
    基于全映射的全自动框架,大量字段的POJO进行部分映射时比较困难。导致数据库性能下降。
  4. 对开发人员而言,核心sql还是需要自己优化
  5. sql和java编码分开,功能边界清晰,一个专注业务、 一个专注数据

MyBatis操作数据库

环境搭建:

  1. 创建MyBatis全局配置文件
    MyBatis 的全局配置文件包含了影响 MyBatis 行为甚深的设置(settings)和属性(properties)信息、如数据库连接池信息等。指导着MyBatis进行工作。我们可以参照官方文件的配置示例。
  2. 创建SQL映射文件
    映射文件的作用就相当于是定义Dao接口的实现类如何工作。这也是我们使用MyBatis时编写的最多的文件。

测试:

  1. 根据全局配置文件,利用 SqlSessionFactoryBuilder创建SqlSessionFactory
  2. 使用SqlSessionFactory获取sqlSession对象。一个 SqlSession对象代表和数据库的一次会话。
  3. 使用SqlSession根据方法id进行操作

接口式编程:

  1. 使用SqlSession获取映射器进行操作

SqlSession:

  1. SqlSession 的实例不是线程安全的,因此是不能被共享的。
  2. SqlSession每次使用完成后需要正确关闭,这个关闭操作是必须的
  3. SqlSession可以直接调用方法的id进行数据库操作,但是我们一般还是推荐使用SqlSession获取
    到Dao接口的代理类,执行代理对象的方法,可以更安全的进行类型检查操作

MyBatis-全局配置文件

MyBatis 的配置文件包含了影响 MyBatis 行为甚深的设置(settings)和属性(properties)信息。文档的顶层结构如下:
• configuration 配置
     • properties 属性
     • settings 设置
     • typeAliases 类型命名
     • typeHandlers 类型处理器
     • objectFactory 对象工厂
     • plugins 插件
     • environments 环境
          • environment 环境变量
               • transactionManager 事务管理器
               • dataSource 数据源
     • databaseIdProvider 数据库厂商标识
     • mappers 映射器

properties属性

<properties resource="dbconfig.properties"></properties>

resource:引入类路径下的资源
url:引入网络路径或者磁盘路径下的资源

如果属性在不只一个地方进行了配置,那么 MyBatis 将按照下面的顺序来加载:

  • 在 properties 元素体内指定的属性首先被读取。
  • 然后根据 properties 元素中的 resource 属性读取类路径下属性文件或根据 url属性指定的路径读取属性文件,并覆盖已读取的同名属性。
  • 最后读取作为方法参数传递的属性,并覆盖已读取的同名属性。

settings设置

这是 MyBatis 中极为重要的调整设置,它们会改变MyBatis 的运行时行为。
在这里插入图片描述

typeAliases别名处理器

• 类型别名是为 Java 类型设置一个短的名字,可以方便我们引用某个类。

<typeAliases>
	<typeAlias type="com.bean.Employee' alisa="employee" />
	<typeAlias type="com.bean.Department' alisa="department" />
<typeAliases/>

• 类很多的情况下,可以批量设置别名这个包下的每一个类创建一个默认的别名,就是简单类名小写。

<typeAliases>
   <package name="com.bean" />
<typeAliases/>

• 也可以使用@Alias注解为其指定一个别名

@Alias("emps")
pubulic class Employee{

值得注意的是,MyBatis已经为许多常见的 Java 类型内建了相应的类型别名。它们都是大小写不敏感的,我们在起别名的时候千万不要占用已有的别名。
在这里插入图片描述

typeHandlers类型处理器

无论是 MyBatis 在预处理语句(PreparedStatement)中设置一个参数时,还是从结果集中取出一个值时, 都会用类型处理器将获取的值以合适的方式转换成 Java 类型。
在这里插入图片描述
日期类型的处理:

  1. 日期和时间的处理,JDK1.8以前一直是个头疼的问题。我们通常使用JSR310规范领导者Stephen
    Colebourne创建的Joda-Time来操作。1.8已经实现全部的JSR310规范了。
  2. 日期时间处理上,我们可以使用MyBatis基于JSR310(Date and Time API)编写的各种日期时间类型处理器。
  3. MyBatis3.4以前的版本需要我们手动注册这些处 理器,以后的版本都是自动注册的
    在这里插入图片描述

自定义类型处理器:
我们可以重写类型处理器或创建自己的类型处理器来处理不支持的或非标准的类型。
步骤:

  1. 实现org.apache.ibatis.type.TypeHandler接口或者继承org.apache.ibatis.type.BaseTypeHandler
  2. 指定其映射某个JDBC类型(可选操作)
  3. 在mybatis全局配置文件中注册

plugins插件

插件是MyBatis提供的一个非常强大的机制,我们可以通过插件来修改MyBatis的一些核心行为。插
件通过动态代理机制,可以介入四大对象的任何一个方法的执行。后面会有专门的章节我们来介绍mybatis运行原理以及插件。
• Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
• ParameterHandler (getParameterObject, setParameters)
• ResultSetHandler (handleResultSets, handleOutputParameters)
• StatementHandler (prepare, parameterize, batch, update, query)

environments环境

  • MyBatis可以配置多种环境,比如开发、测试和生产环境需要有不同的配置。
  • 每种环境使用一个environment标签进行配置并指定唯一标识符
  • 可以通过environments标签中的default属性指定一个环境的标识符来快速的切换环境

environment-指定具体环境:

  • id:指定当前环境的唯一标识
  • transactionManager和dataSource都必须有

transactionManager
• type: JDBC | MANAGED | 自定义

  1. JDBC:使用了 JDBC 的提交和回滚设置,依赖于从数据源得到的连接来管理事务范围。JdbcTransactionFactory
  2. MANAGED:不提交或回滚一个连接、让容器来管理事务的整个生命周期(比如 JEE 应用服务器的上下文)。ManagedTransactionFactory
  3. 自定义:实现TransactionFactory接口,type=全类名/别名

dataSource
• type: UNPOOLED | POOLED | JNDI | 自定义

  1. UNPOOLED:不使用连接池, UnpooledDataSourceFactory
  2. POOLED:使用连接池, PooledDataSourceFactory
  3. JNDI: 在EJB 或应用服务器这类容器中查找指定的数 据源
  4. 自定义:实现DataSourceFactory接口,定义数据源的 获取方式。

实际开发中我们使用Spring管理数据源,并进行事务控制的配置来覆盖上述配置

<environments default="dev_mysql">
		<environment id="dev_mysql">
			<transactionManager type="JDBC"></transactionManager>
			<dataSource type="POOLED">
				<property name="driver" value="${jdbc.driver}" />
				<property name="url" value="${jdbc.url}" />
				<property name="username" value="${jdbc.username}" />
				<property name="password" value="${jdbc.password}" />
			</dataSource>
		</environment>
	
		<environment id="dev_oracle">
			<transactionManager type="JDBC" />
			<dataSource type="POOLED">
				<property name="driver" value="${orcl.driver}" />
				<property name="url" value="${orcl.url}" />
				<property name="username" value="${orcl.username}" />
				<property name="password" value="${orcl.password}" />
			</dataSource>
		</environment>
	</environments>

Mybatis事务

DML与事务:
对于DML数据操作,我们必须要记住提交事务,如果autocommit为true的话,当然就不用我们自己操心了!!数据库会帮我们提交的!!但是在MyBatis持久层框架中,进行DML操作时我们必须要手动开启事务,并且手动提交事务!!因为在MyBatis持久层框架中,它们处理DML语句的时候会自动设置autocommit=false;如果DML中不进行手动提交事务,那么最后事务就会进行回滚。

为什么不设置autocommit=true自动提交呢?
因为autocommit是针对于单条sql语句的自动提交。反而我们真实写项目的时候,用到的事务都是包含多条sql语句,所以我们不得不自己手动显示开启事务,并且手动进行事务提交!!以此来创建一个事务作用边界。

DQL与事务:
MyBatis框架中DQL不需要我们手动开启事务或者提交事务。我们不做同样能够读取到最新数据!!DQL的事务开启或者提交是框架帮我们做!!

DQL不同隔离级别查询的值会不同,针对于MySQL,InnoDB的REPEATABLE_READ,其保证了同一个事务里,查询的结果和开启事务时并且第一次查询数据时的数据总是一样的。

有很多程序员说,DQL不需要开启事务,但是通过我们的最佳实践知道,就算我们不开启事务,数据库也会帮我们开启事务。而且在一个事务中查询数据还会一直相同(REPEATABLE_READ事务隔离级别)。所以这和很多程序员的观点:”DQL不需要开启事务”是矛盾的!

databaseIdProvider环境

MyBatis 可以根据不同的数据库厂商执行不同的语句。

Type: DB_VENDOR
– 使用MyBatis提供的VendorDatabaseIdProvider解析数据库厂商标识。也可以实现DatabaseIdProvider接口来自定义。
• Property-name:数据库厂商标识
• Property-value:为标识起一个别名,方便SQL语句使用databaseId属性引用

<databaseIdProvider type="DB_VENDOR">
		<!-- 为不同的数据库厂商起别名 -->
		<property name="MySQL" value="mysql"/>
		<property name="Oracle" value="oracle"/>
		<property name="SQL Server" value="sqlserver"/>
</databaseIdProvider>

mapper映射

• mapper逐个注册SQL映射文件

<mappers>
	<mapper resource="mybatis/mapper/EmployeeMapper.xml"/> 
	<mapper url="file:///D:UserDao.xml"/> 
 	<mapper class="com.mybatis.dao.EmployeeMapperAnnotation"/> 
 <mappers/>

• 或者使用批量注册:
          • 这种方式要求SQL映射文件名必须和接口名相同并且在同一目录下

<mappers>
	<package name="com.mybatis.dao"/>
 <mappers/>

注册接口
class:引用(注册)接口,
1、有sql映射文件,映射文件名必须和接口同名,并且放在与接口同一目录下;
2、没有sql映射文件,所有的sql都是利用注解写在接口上;
推荐:
比较重要的,复杂的Dao接口我们来写sql映射文件
不重要,简单的Dao接口为了开发快速可以使用注解。

MyBatis-映射文件

映射文件指导着MyBatis如何进行数据库增删改查,有着非常重要的意义。
•cache –命名空间的二级缓存配置
•cache-ref – 其他命名空间缓存配置的引用。
•resultMap – 自定义结果集映射
•parameterMap – 已废弃!老式风格的参数映射
•sql –抽取可重用语句块。
•insert – 映射插入语句
•update – 映射更新语句
•delete – 映射删除语句
•select – 映射查询语句

insert、update、delete元素

在这里插入图片描述
主键生成方式:

  1. 若数据库支持自动生成主键的字段(比如 MySQL 和 SQL Server),则可以设置useGeneratedKeys=”true”,然后再把keyProperty 设置到目标属性上。
  2. 而对于不支持自增型主键的数据库(例如 Oracle),则可以使用 selectKey 子元素: selectKey
    元素将会首先运行,id 会被设置,然后插入语句会被调用
    selectKey:
    在这里插入图片描述

参数(Parameters)传递

  1. 单个参数
    可以接受基本类型,对象类型,集合类型的值。这种情况MyBatis可直接使用这个参数,不需要经过任何处理。
  2. 多个参数
    任意多个参数,都会被MyBatis重新包装成一个Map传入。Map的key是param1,param2,0,1…,值就是参数的值。(通常我们习惯用参数的nama作为key,因此需要使用命名参数)
  3. 命名参数
    为参数使用@Param起一个名字,MyBatis就会将这些参数封装进map中,key就是我们自己指定的名字
  4. POJO
    当这些参数属于我们业务POJO时,我们直接传递POJO
  5. Map
    我们也可以封装多个参数为map,直接传递
  6. DTO
    当参数过多,切该Map需要经常使用时,可以将Map封装成一个DTO对象。

参数处理

  • 参数位置支持的属性:
    javaType、jdbcType、mode、numericScale、
    resultMap、typeHandler、jdbcTypeName、expression
  • 实际上通常被设置的是: 可能为空的列名指定 jdbcType
  • #{key}:获取参数的值,预编译到SQL中。安全。
  • ${key}:获取参数的值,拼接到SQL中。有SQL注入问题。ORDER BY ${name}

select元素

Select元素来定义查询操作。

  • Id:唯一标识符。 用来引用这条语句,需要和接口的方法名一致
  • parameterType:参数类型。可以不传,MyBatis会根据TypeHandler自动推断
  • resultType:返回值类型。 别名或者全类名,如果返回的是集合,定义集合中元 素的类型。不能和resultMap同时使用
    在这里插入图片描述

自动映射

  • 全局setting设置
    一、autoMappingBehavior默认是PARTIAL,开启自动映射的功能。唯一的要求是列名和javaBean属性名一致
    二、如果autoMappingBehavior设置为null则会取消自动映射
    三、数据库字段命名规范,POJO属性符合驼峰命名法,如A_COLUMNaColumn,我们可以开启自动驼峰命名规则映射功能,mapUnderscoreToCamelCase=true。
  • 自定义resultMap,实现高级结果集映射。

resultMap

• constructor - 类在实例化时, 用来注入结果到构造方法中
     – idArg - ID 参数; 标记结果作为 ID 可以帮助提高整体效能
     – arg - 注入到构造方法的一个普通结果
• id – 一个 ID 结果; 标记结果作为 ID 可以帮助提高整体效能
• result – 注入到字段或 JavaBean 属性的普通结果
association – 一个复杂的类型关联;许多结果将包成这种类型
     – 嵌入结果映射 – 结果映射自身的关联,或者参考一个
collection – 复杂类型的集
     – 嵌入结果映射 – 结果映射自身的集,或者参考一个
• discriminator – 使用结果值来决定使用哪个结果映射
     – case – 基于某些值的结果映射
          •嵌入结果映射 – 这种情形结果也映射它本身,因此可以包含很多相同的元素,或者它可以参照一个外部的结果映射。

id & result

id 和 result 映射一个单独列的值到简单数据类型(字符串,整型,双精度浮点数,日期等)的属性或字段。
在这里插入图片描述

association

  • 复杂对象映射
  • POJO中的属性可能会是一个对象
  • 我们可以使用联合查询,并以级联属性的方式封装对象。
  • 使用association标签定义对象的封装规则

association-嵌套结果集:

<resultMap type="com.bean.Lock" id="myLock">
	<id column="id" property="id"/>
	<result column="lock_name" property="lockName"/>
	<association property="key" javaType="com.bean.Key">
		<id column="key_id" property="id"/>
		<result column="key_name" property="keyName"/>
	</association>
</resultMap>

association-分段查询:

<resultMap type="com.bean.Lock" id="myLock">
	<id column="id" property="id"/>
	<result column="lock_name" property="lockName"/>
	<association property="key"
		select="com.dao.KeyMapper.getKeyId"
		column="key_id">
	</association>
</resultMap>

select:调用目标的方法查询当前属性的值
column:将指定列的值传入目标方法

association-分段查询&延迟加载:
开启延迟加载和属性按需加载

<settings>
	<setting name="LazyLoadingEnabled" value="true">
	<setting name="aggressiveLazyLoading" value="false">
</settings>

Collection

Collection-集合类型&嵌套结果集:

<resultMap type="com.bean.Department" id="myDeptSetp">
	<id column="id" property="id"/>
	<result column="dept_name" property="deptName"/>
	<collection property="emps" ofType="com.bean.Employee"
		columnPrefix="e_">
		<id column="id" property="id"/>
		<result column="last_name" property="lastName"/>
		<result column="emial" property="emial"/>
		<result column="gender" property="gender"/>
	</collection>
</resultMap>

Collection-分步查询:

<resultMap type="com.bean.Department" id="myDeptSetp">
	<id column="id" property="id"/>
	<result column="dept_name" property="deptName"/>
	<collection property="emps"
		select="com.dao.EmployeeMapper.getEmpsByDeptId"
		column="id">
	</collection>
</resultMap>

Collection-分步查询&延迟加载:
开启延迟加载和属性按需加载

<settings>
	<setting name="LazyLoadingEnabled" value="true">
	<setting name="aggressiveLazyLoading" value="false">
</settings>

扩展-多列值封装map传递

  1. 分步查询的时候通过column指定,将对应的列的数据传递过去,我们有时需要传递多列数据。
    使用{key1=column1,key2=column2…}的形式
<resultMap type="com.bean.Department" id="myDeptSetp">
	<id column="id" property="id"/>
	<result column="dept_name" property="deptName"/>
	<collection property="emps"
		select="com.dao.EmployeeMapper.getEmpsByDeptId"
		column="{deptId=id}">
	</collection>
</resultMap>
  1. association或者collection标签的 fetchType=eager/lazy可以覆盖全局的延迟加载策略,指定立即加载(eager)或者延迟加载(lazy)

MyBatis-动态SQL

  • 动态 SQL是MyBatis强大特性之一。极大的简化我们拼装 SQL的操作。
  • 动态 SQL 元素和使用 JSTL 或其他类似基于 XML 的文本处 理器相似。
  • MyBatis 采用功能强大的基于 OGNL 的表达式来简化操作。
    –if
    – choose (when, otherwise)
    – trim (where, set)
    – foreach

其中set是update语句中的set。

MySQL下批量保存:
MySQL下批量保存可以foreach遍历。
mysql支持values(),(),()语法。
这种方式需要数据库连接属性allowMultiQueries=true;
这种分号分隔多个sql可以用于其他的批量操作(删除,修改)

foreach:

  • 动态 SQL 的另外一个常用的必要操作是需要对一个集合进行遍历,通常是在构建 IN 条件语句的时候。
  • 当迭代列表、集合等可迭代对象或者数组时 – index是当前迭代的次数,item的值是本次迭代获取的元素
  • 当使用字典(或者Map.Entry对象的集合)时 – index是键,item是值

两个内置参数:
mybatis默认还有两个内置参数:
_parameter:代表整个参数
     单个参数:_parameter就是这个参数
     多个参数:参数会被封装为一个map;_parameter就是代表这个map
_databaseId:如果配置了databaseIdProvider标签。_databaseId就是代表当前数据库的别名

bind:
bind 元素可以从 OGNL 表达式中创建一个变量并将其绑定到上下文。比如:

<select id="getEmpByLastNameLike" resultType="com.bean.Employee">
	<bind name="myLastName" value="'%'+lastName+'%'"/>
	select * from employee where last_name like #{myLastName}
</select>

抽取可重用的sql片段:
抽取可重用的sql片段方便后面引用
1、sql抽取:经常将要查询的列名,或者插入用的列名抽取出来方便引用
2、include来引用已经抽取的sql:
3、include还可以自定义一些property,sql标签内部就能使用自定义的属性

MyBatis-缓存机制

MyBatis 包含一个非常强大的查询缓存特性,它可以非常方便地配置和定制。
缓存可以极大的提升查询效率。
• MyBatis系统中默认定义了两级缓存。
• 一级缓存和二级缓存。
– 1、默认情况下,只有一级缓存(SqlSession级别的缓存,也称为本地缓存)开启。
– 2、二级缓存需要手动开启和配置,他是基于namespace级别的缓存。
– 3、为了提高扩展性。MyBatis定义了缓存接口Cache。我们可以通过实现Cache接口来自定义二级缓存

一级缓存

一级缓存(local cache), 即本地缓存, 作用域默认为sqlSession。当 Session flush 或 close 后, 该Session 中的所有 Cache 将被清空。
• 本地缓存不能被关闭, 但可以调用 clearCache() 来清空本地缓存, 或者改变缓存的作用域.
• 在mybatis3.1之后, 可以配置本地缓存的作用域. 在 mybatis.xml 中配置

一级缓存演示&失效情况:
同一次会话期间只要查询过的数据都会保存在当前SqlSession的一个Map中
• 一级缓存失效的四种情况
– 1、不同的SqlSession对应不同的一级缓存
– 2、同一个SqlSession但是查询条件不同
– 3、同一个SqlSession两次查询期间执行了任何一次增删改操作
– 4、同一个SqlSession两次查询期间手动清空了缓存

二级缓存

• 二级缓存(second level cache),全局作用域缓存
• 二级缓存默认不开启,需要手动配置
• MyBatis提供二级缓存的接口以及实现,缓存实现要求POJO实现Serializable接口
• 二级缓存在 SqlSession 关闭或提交之后才会生效
• 使用步骤
– 1、全局配置文件中开启二级缓存
     • <setting name=“cacheEnabled” value=“true”/>
– 2、需要使用二级缓存的映射文件处使用cache配置缓存
     • <cache />
– 3、注意:POJO需要实现Serializable接口

缓存相关属性:
• eviction=“FIFO”:缓存回收策略:
     • LRU – 最近最少使用的:移除最长时间不被使用的对象。
     • FIFO – 先进先出:按对象进入缓存的顺序来移除它们。
     • SOFT – 软引用:移除基于垃圾回收器状态和软引用规则的对象。
     • WEAK – 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。
     • 默认的是 LRU。
• flushInterval:刷新间隔,单位毫秒
     • 默认情况是不设置,也就是没有刷新间隔,缓存仅仅调用语句时刷新
• size:引用数目,正整数
     • 代表缓存最多可以存储多少个对象,太大容易导致内存溢出
• readOnly:只读,true/false
     • true:只读缓存;会给所有调用者返回缓存对象的相同实例。因此这些对象不能被修改。这提供了很重要的性能优势。
     • false:读写缓存;会返回缓存对象的拷贝(通过序列化)。这会慢一些,但是安全,因此默认是 false。

缓存有关设置:
• 1、全局setting的cacheEnable:
     – 配置二级缓存的开关。一级缓存一直是打开的。
• 2、select标签的useCache属性:
     – 配置这个select是否使用二级缓存。一级缓存一直是使用的
• 3、sql标签的flushCache属性:
     – 增删改默认flushCache=true。sql执行以后,会同时清空一级和二级缓存。查询默认flushCache=false。
• 4、sqlSession.clearCache():
     – 只是用来清除一级缓存。
• 5、当在某一个作用域 (一级缓存Session/二级缓存Namespaces) 进行了 C/U/D 操作后,默认该作用域下所 有 select 中的缓存将被clear。

第三方缓存整合

•EhCache 是一个纯Java的进程内缓存框架,具有快速、精干等特点,是Hibernate中默认的CacheProvider。
• MyBatis定义了Cache接口方便我们进行自定义扩展。
• 步骤:
     – 1、导入ehcache包,以及整合包,日志包ehcache-core-2.6.8.jar、mybatis-ehcache-1.0.3.jar
slf4j-api-1.6.1.jar、slf4j-log4j12-1.6.2.jar
     – 2、编写ehcache.xml配置文件
     – 3、配置cache标签
          – <cache type=“org.mybatis.caches.ehcache.EhcacheCache”></cache>
• 参照缓存:若想在命名空间中共享相同的缓存配置和实例。可以使用 cache-ref 元素来引用另外一个缓存

缓存流程

在这里插入图片描述

MyBatis-Spring整合

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
	<!-- 指定mybatis全局配置文件位置 --> 
	<property name="configLocation" value="classpath:mybatis/mybatis-config.xml"></property>
	<!--指定数据源 -->
 	<property name="dataSource" ref="dataSource"></property>
	<!--mapperLocations:所有sql映射文件所在的位置 --> 
	<property name="mapperLocations" value="classpath:mybatis/mapper/*.xml"></property>
	<!--typeAliasesPackage:批量别名处理--> 
	<property name="typeAliasesPackage" value="com.atguigu.bean"></property>
</bean>

<!--自动的扫描所有的mapper的实现并加入到ioc容器中 --> 
<bean id="configure" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
	<!– basePackage:指定包下所有的mapper接口实现自动扫描并加入到ioc容器中 --> 
	<property name="basePackage" value="com.atguigu.dao"></property>
</bean>

本文地址:https://blog.csdn.net/weixin_41005188/article/details/107438362

如对本文有疑问, 点击进行留言回复!!

相关文章:

验证码:
移动技术网