




版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請(qǐng)進(jìn)行舉報(bào)或認(rèn)領(lǐng)
文檔簡(jiǎn)介
第Mybatis攔截器實(shí)現(xiàn)自定義需求目錄前言一、應(yīng)用場(chǎng)景二、Mybatis實(shí)現(xiàn)自定義攔截器2.1、編寫攔截器2.2、添加到Mybatis配置2.3、測(cè)試2.4、小結(jié)三、攔截器接口介紹intercept方法plugin方法setProperties方法注意四、攔截器注解介紹Executor接口ParameterHandler接口ResultSetHandler接口StatementHandler接口五、進(jìn)一步思考
前言
最近在參加金石計(jì)劃,在考慮寫什么的時(shí),想到自己在項(xiàng)目中使用過的mybatis的插件,就想趁這個(gè)機(jī)會(huì)聊一聊我們接觸頻繁的Mybatis.
如果是使用過Mybatis的小伙伴,那么我們接觸過的第一個(gè)Mybatis的插件自然就是分頁插件(MybatisHelper)啦。
你有了解過它是如何實(shí)現(xiàn)的嗎?你有沒有自己編寫Mybatis插件去實(shí)現(xiàn)一些自定義需求呢?
插件是一種常見的擴(kuò)展方式,大多數(shù)開源框架也都支持用戶通過添加自定義插件的方式來擴(kuò)展或改變框架原有的功能。
Mybatis中也提供了插件的功能,雖然叫插件,但是實(shí)際上是通過攔截器(Interceptor)實(shí)現(xiàn)的,通過攔截某些方法的調(diào)用,在執(zhí)行目標(biāo)邏輯之前插入我們自己的邏輯實(shí)現(xiàn)。另外在MyBatis的插件模塊中還涉及責(zé)任鏈模式和JDK動(dòng)態(tài)代理~
文章大綱:
一、應(yīng)用場(chǎng)景
一些字段的自動(dòng)填充SQL語句監(jiān)控、打印、數(shù)據(jù)權(quán)限等數(shù)據(jù)加解密操作、數(shù)據(jù)脫敏操作分頁插件參數(shù)、結(jié)果集的類型轉(zhuǎn)換
這些都是一些可以使用Mybatis插件實(shí)現(xiàn)的場(chǎng)景,當(dāng)然也可以使用其他的方式來實(shí)現(xiàn),只不過攔截的地方不一樣罷了,有早有晚。
二、Mybatis實(shí)現(xiàn)自定義攔截器
我們用自定義攔截器實(shí)現(xiàn)一個(gè)相對(duì)簡(jiǎn)單的需求,在大多數(shù)表設(shè)計(jì)中,都會(huì)有create_time和update_time等字段,在創(chuàng)建或更新時(shí)需要更新相關(guān)字段。
如果是使用過MybatisPlus的小伙伴,可能知道在MybatisPlus中有一個(gè)自動(dòng)填充功能,通過實(shí)現(xiàn)MetaObjectHandler接口中的方法來進(jìn)行實(shí)現(xiàn)(主要的實(shí)現(xiàn)代碼在com.baomidou.mybatisplus.core.MybatisParameterHandler中).
但使用Mybatis,并沒有相關(guān)的方法或API可以直接來實(shí)現(xiàn)。所以我們這次就用以此處作為切入點(diǎn),自定義攔截器來實(shí)現(xiàn)類似的自動(dòng)填充功能。
編寫步驟
編寫一個(gè)攔截器類實(shí)現(xiàn)Interceptor接口添加攔截注解@Intercepts在xml文件中配置攔截器或者添加到Configuration中
基礎(chǔ)的環(huán)境我就不再貼出來啦哈,直接上三個(gè)步驟的代碼
2.1、編寫攔截器
packageerceptor;
importlombok.extern.slf4j.Slf4j;
importorg.apache.ibatis.executor.Executor;
importorg.apache.ibatis.executor.parameter.ParameterHandler;
importorg.apache.ibatis.executor.resultset.ResultSetHandler;
importorg.apache.ibatis.executor.statement.StatementHandler;
importorg.apache.ibatis.mapping.MappedStatement;
importorg.apache.ibatis.mapping.SqlCommandType;
importorg.apache.ibatis.plugin.Interceptor;
importorg.apache.ibatis.plugin.Intercepts;
importorg.apache.ibatis.plugin.Invocation;
importorg.apache.ibatis.plugin.Signature;
importorg.springframework.beans.factory.annotation.Value;
importjava.lang.reflect.Field;
importjava.util.*;
*@author寧在春
*@version1.0
*@description:通過實(shí)現(xiàn)攔截器來實(shí)現(xiàn)部分字段的自動(dòng)填充功能
*@date2025/4/621:49
@Intercepts({
@Signature(type=Executor.class,method="update",args={MappedStatement.class,Object.class})
@Slf4j
publicclassMybatisMetaInterceptorimplementsInterceptor{
@Override
publicObjectintercept(Invocationinvocation)throwsThrowable{
MappedStatementmappedStatement=(MappedStatement)invocation.getArgs()[0];
StringsqlId=mappedStatement.getId();
("sqlId"+sqlId);
SqlCommandTypesqlCommandType=mappedStatement.getSqlCommandType();
Objectparameter=invocation.getArgs()[1];
("sqlCommandType"+sqlCommandType);
("攔截查詢請(qǐng)求Executor#update方法"+invocation.getMethod());
if(parameter==null){
returnceed();
if(SqlCommandType.INSERT==sqlCommandType){
Field[]fields=getAllFields(parameter);
for(Fieldfield:fields){
(""+field.getName());
try{
//注入創(chuàng)建時(shí)間
if("createTime".equals(field.getName())){
field.setAccessible(true);
Objectlocal_createDate=field.get(parameter);
field.setAccessible(false);
if(local_createDate==null||local_createDate.equals("")){
field.setAccessible(true);
field.set(parameter,newDate());
field.setAccessible(false);
}catch(Exceptione){
if(SqlCommandType.UPDATE==sqlCommandType){
Field[]fields=getAllFields(parameter);
for(Fieldfield:fields){
(""+field.getName());
try{
if("updateTime".equals(field.getName())){
field.setAccessible(true);
field.set(parameter,newDate());
field.setAccessible(false);
}catch(Exceptione){
e.printStackTrace();
returnceed();
@Override
publicObjectplugin(Objecttarget){
returnInterceptor.super.plugin(target);
//稍后會(huì)展開說的
@Override
publicvoidsetProperties(Propertiesproperties){
System.out.println("=======begin");
System.out.println(properties.getProperty("param1"));
System.out.println(properties.getProperty("param2"));
Interceptor.super.setProperties(properties);
System.out.println("=======end");
*獲取類的所有屬性,包括父類
*@paramobject
*@return
publicstaticField[]getAllFields(Objectobject){
Classclazz=object.getClass();
ListFieldfieldList=newArrayList();
while(clazz!=null){
fieldList.addAll(newArrayList(Arrays.asList(clazz.getDeclaredFields())));
clazz=clazz.getSuperclass();
Field[]fields=newField[fieldList.size()];
fieldList.toArray(fields);
returnfields;
?
2.2、添加到Mybatis配置
我這里使用的JavaConfig的方式
packagecom.nzc.config;
importerceptor.*;
importorg.mybatis.spring.boot.autoconfigure.ConfigurationCustomizer;
importorg.springframework.context.annotation.Bean;
importorg.springframework.context.annotation.Configuration;
@Configuration
publicclassMyBatisConfig{
@Bean
publicConfigurationCustomizerconfigurationCustomizer(){
returnnewConfigurationCustomizer(){
@Override
publicvoidcustomize(org.apache.ibatis.session.Configurationconfiguration){
//開啟駝峰命名映射
configuration.setMapUnderscoreToCamelCase(true);
MybatisMetaInterceptormybatisMetaInterceptor=newMybatisMetaInterceptor();
Propertiesproperties=newProperties();
properties.setProperty("param1","javaconfig-value1");
properties.setProperty("param2","javaconfig-value2");
mybatisMetaInterceptor.setProperties(properties);
configuration.addInterceptor(mybatisMetaInterceptor);
}
如果是xml配置的話,則是如下:property是設(shè)置攔截器中需要用到的參數(shù)
configuration
plugins
plugininterceptor="erceptor.MybatisMetaInterceptor"
propertyname="param1"value="value1"/
propertyname="param2"value="value2"/
/plugin
/plugins
/configuration
2.3、測(cè)試
測(cè)試代碼:實(shí)現(xiàn)了一個(gè)SysMapper的增刪改查
packagecom.nzc.mapper;
importcom.nzc.entity.SysUser;
importorg.apache.ibatis.annotations.Insert;
importorg.apache.ibatis.annotations.Mapper;
importorg.apache.ibatis.annotations.Select;
importorg.apache.ibatis.annotations.Update;
importjava.util.List;
*@author寧在春
*@description針對(duì)表【sys_user】的數(shù)據(jù)庫(kù)操作Mapper
@Mapper
publicinterfaceSysUserMapper{
@Select("SELECT*FROMtb_sys_user")
ListSysUserlist();
@Insert("insertintotb_sys_user(id,username,realname,create_time,update_time)values(#{id},#{username},#{realname},#{createTime},#{updateTime})")
Booleaninsert(SysUsersysUser);
@Update("updatetb_sys_usersetusername=#{username},realname=#{realname},update_time=#{updateTime}whereid=#{id}")
booleanupdate(SysUsersysUser);
}
/**
*@author寧在春
*@version1.0
*@description:TODO
*@date2025/4/621:38
@Slf4j
@RunWith(SpringRunner.class)
@SpringBootTest
publicclassSysUserMapperTest{
@Autowired
privateSysUserMappersysUserMapper;
@Test
publicvoidtest1(){
System.out.println(sysUserMapper.list());
@Test
publicvoidtest2(){
SysUsersysUser=newSysUser();
sysUser.setId("1235");
sysUser.setUsername("nzc5");
sysUser.setRealname("nzc5");
System.out.println(sysUserMapper.insert(sysUser));
@Test
publicvoidtest3(){
SysUsersysUser=newSysUser();
sysUser.setId("1235");
sysUser.setUsername("nzc7");
sysUser.setRealname("nzc5");
System.out.println(sysUserMapper.update(sysUser));
?
當(dāng)然重點(diǎn)不在這里,而是在我們打印的日志上,一起來看看效果吧
此處相關(guān)日志對(duì)應(yīng)Interceptor中的日志打印,想要了解的更為詳細(xì)的可以debug查看一番。
2.4、小結(jié)
通過這個(gè)小小的案例,我想大伙對(duì)于Mybatis中的攔截器應(yīng)當(dāng)是沒有那般陌生了吧,接下來再來仔細(xì)聊聊吧
如果你使用過MybatisPlus的話,在讀完這篇博文后,可以思考思考下面這個(gè)問題,或去看一看源碼,將知識(shí)串聯(lián)起來,如果可以的話,記得把答案貼到評(píng)論區(qū)啦~~~
思考:還記得這一小節(jié)開始我們聊到的MybatisPlus實(shí)現(xiàn)的自動(dòng)填充功能嗎?它是怎么實(shí)現(xiàn)的呢?
三、攔截器接口介紹
MyBatis插件可以用來實(shí)現(xiàn)攔截器接口Interceptor,在實(shí)現(xiàn)類中對(duì)攔截對(duì)象和方法進(jìn)行處理
publicinterfaceInterceptor{
//執(zhí)行攔截邏輯的方法
Objectintercept(Invocationinvocation)throwsThrowable;
//這個(gè)方法的參數(shù)target就是攔截器要攔截的對(duì)象,該方法會(huì)在創(chuàng)建被攔截的接口實(shí)現(xiàn)類時(shí)被調(diào)用。
//該方法的實(shí)現(xiàn)很簡(jiǎn)單,只需要調(diào)用MyBatis提供的Plug類的wrap靜態(tài)方法就可以通過Java動(dòng)態(tài)代理攔截目標(biāo)對(duì)象。
defaultObjectplugin(Objecttarget){
returnPlugin.wrap(target,this);
//這個(gè)方法用來傳遞插件的參數(shù),可以通過參數(shù)來改變插件的行為
defaultvoidsetProperties(Propertiesproperties){
//NOP
}
有點(diǎn)懵沒啥事,一個(gè)一個(gè)展開說:
intercept方法
Objectintercept(Invocationinvocation)throwsThrowable;
簡(jiǎn)單說就是執(zhí)行攔截邏輯的方法,但不得不說這句話是個(gè)高度概括~
首先我們要明白參數(shù)Invocation是個(gè)什么東東:
publicclassInvocation{
privatefinalObjecttarget;//攔截的對(duì)象信息
privatefinalMethodmethod;//攔截的方法信息
privatefinalObject[]args;//攔截的對(duì)象方法中的參數(shù)
publicInvocation(Objecttarget,Methodmethod,Object[]args){
this.target=target;
this.method=method;
this.args=args;
//get...
//利用反射來執(zhí)行攔截對(duì)象的方法
publicObjectproceed()throwsInvocationTargetException,IllegalAccessException{
returnmethod.invoke(target,args);
}
聯(lián)系我們之前實(shí)現(xiàn)的自定義攔截器上的注解:
@Intercepts({
@Signature(type=Executor.class,method="update",args={MappedStatement.class,Object.class})
})
target對(duì)應(yīng)我們攔截的Executor對(duì)象method對(duì)應(yīng)Executor#update方法args對(duì)應(yīng)Executor#update#args參數(shù)
plugin方法
這個(gè)方法其實(shí)也很好說:
那就是Mybatis在創(chuàng)建攔截器代理時(shí)候會(huì)判斷一次,當(dāng)前這個(gè)類Interceptor到底需不需要生成一個(gè)代理進(jìn)行攔截,如果需要攔截,就生成一個(gè)代理對(duì)象,這個(gè)代理就是一個(gè){@linkPlugin},它實(shí)現(xiàn)了jdk的動(dòng)態(tài)代理接口{@linkInvocationHandler},如果不需要代理,則直接返回目標(biāo)對(duì)象本身加載時(shí)機(jī):該方法在mybatis加載核心配置文件時(shí)被調(diào)用
defaultObjectplugin(Objecttarget){
returnPlugin.wrap(target,this);
}
publicclassPluginimplementsInvocationHandler{
//利用反射,獲取這個(gè)攔截器MyInterceptor的注解Intercepts和Signature,然后解析里面的值,
//1先是判斷要攔截的對(duì)象是哪一個(gè)
//2然后根據(jù)方法名稱和參數(shù)判斷要對(duì)哪一個(gè)方法進(jìn)行攔截
//3根據(jù)結(jié)果做出決定,是返回一個(gè)對(duì)象呢還是代理對(duì)象
publicstaticObjectwrap(Objecttarget,Interceptorinterceptor){
MapClass,SetMethodsignatureMap=getSignatureMap(interceptor);
Classtype=target.getClass();
//這邊就是判斷當(dāng)前的interceptor是否包含在
Class[]interfaces=getAllInterfaces(type,signatureMap);
if(interfaces.length0){
returnProxy.newProxyInstance(
type.getClassLoader(),
interfaces,
newPlugin(target,interceptor,signatureMap));
//如果不需要代理,則直接返回目標(biāo)對(duì)象本身
returntarget;
//
}
setProperties方法
在攔截器中可能需要使用到一些變量參數(shù),并且這個(gè)參數(shù)是可配置的,這個(gè)時(shí)候我們就可以使用這個(gè)方法啦,加載時(shí)機(jī):該方法在mybatis加載核心配置文件時(shí)被調(diào)用
defaultvoidsetProperties(Propertiesproperties){
//NOP
}
關(guān)于如何使用:
javaConfig方式設(shè)置:
@Bean
publicConfigurationCustomizerconfigurationCustomizer(){
returnnewConfigurationCustomizer(){
@Override
publicvoidcustomize(org.apache.ibatis.session.Configurationconfiguration){
//開啟駝峰命名映射
configuration.setMapUnderscoreToCamelCase(true);
MybatisMetaInterceptormybatisMetaInterceptor=newMybatisMetaInterceptor();
Propertiesproperties=newProperties();
properties.setProperty("param1","javaconfig-value1");
properties.setProperty("param2","javaconfig-value2");
mybatisMetaInterceptor.setProperties(properties);
configuration.addInterceptor(mybatisMetaInterceptor);
}
通過mybatis-config.xml文件進(jìn)行配置
configuration
plugins
plugininterceptor="erceptor.MybatisMetaInterceptor"
propertyname="param1"value="value1"/
propertyname="param2"value="value2"/
/plugin
/plugins
/configuration
測(cè)試效果就是測(cè)試案例上那般,通過了解攔截器接口的信息,對(duì)于之前的案例不再是那般模糊啦
接下來再接著聊一聊攔截器上面那一坨注解信息是用來干嘛的吧,
注意
當(dāng)配置多個(gè)攔截器時(shí),MyBatis會(huì)遍歷所有攔截器,按順序執(zhí)行攔截器的plugin口方法,被攔截的對(duì)象就會(huì)被層層代理。
在執(zhí)行攔截對(duì)象的方法時(shí),會(huì)一層層地調(diào)用攔截器,攔截器通invocationproceed()調(diào)用下層的方法,直到真正的方法被執(zhí)行。
方法執(zhí)行的結(jié)果從最里面開始向外層層返回,所以如果存在按順序配置的三個(gè)簽名相同的攔截器,MyBaits會(huì)按照CBAceed()ABC的順序執(zhí)行。如果簽名不同,就會(huì)按照MyBatis攔截對(duì)象的邏輯執(zhí)行.
這也是我們最開始談到的Mybatis插件模塊所使用的設(shè)計(jì)模式-責(zé)任鏈模式。
四、攔截器注解介紹
上一個(gè)章節(jié),我們只說明如何實(shí)現(xiàn)Interceptor接口來實(shí)現(xiàn)攔截,卻沒有說明要攔截的對(duì)象是誰,在什么時(shí)候進(jìn)行攔截.就關(guān)系到我們之前編寫的注解信息啦.
@Intercepts({
@Signature(type=Executor.class,method="update",args={MappedStatement.class,Object.class})
})
這兩個(gè)注解用來配置攔截器要攔截的接口的方法。
@Intercepts({})注解中是一個(gè)@Signature()數(shù)組,可以在一個(gè)攔截器中同時(shí)攔截不同的接口和方法。
MyBatis允許在己映射語句執(zhí)行過程中的某一點(diǎn)進(jìn)行攔截調(diào)用。默認(rèn)情況下,MyBatis允許使用插件來攔截的接口包括以下幾個(gè)。
ExecutorParameterHandlerResultSetHandlerStatementHandler
@Signature注解包含以下三個(gè)屬性。
type設(shè)置攔截接口,可選值是前面提到的4個(gè)接口method設(shè)置攔截接口中的方法名可選值是前面4個(gè)接口中所對(duì)應(yīng)的方法,需要和接口匹配args設(shè)置攔截方法的參數(shù)類型數(shù)組通過方法名和參數(shù)類型可以確定唯一一個(gè)方法
Executor接口
下面就是Executor接口的類信息
publicinterfaceExecutor{
intupdate(MappedStatementms,Objectparameter)throwsSQLException;
EListEquery(MappedStatementms,Objectparameter,RowBoundsrowBounds,ResultHandlerresultHandler,CacheKeycacheKey,BoundSqlboundSql)throwsSQLException;
EListEquery(MappedStatementms,Objectparameter,RowBoundsrowBounds,ResultHandlerresultHandler)throwsSQLException;
ECursorEqueryCursor(MappedStatementms,Objectparameter,RowBoundsrowBounds)throwsSQLException;
ListBatchResultflushStatements()throwsSQLException;
voidcommit(booleanrequired)throwsSQLException;
voidrollback(booleanrequired)throwsSQLException;
CacheKeycreateCacheKey(MappedStatementms,ObjectparameterObject,RowBoundsrowBounds,BoundSqlboundSql);
booleanisCached(MappedStatementms,CacheKeykey);
voidclearLocalCache();
voiddeferLoad(MappedStatementms,MetaObjectresultObject,Stringproperty,CacheKeykey,ClasstargetType);
TransactiongetTransaction();
voidclose(booleanforceRollback);
booleanisClosed();
voidsetExecutorWrapper(Executorexecutor);
}
我只會(huì)簡(jiǎn)單說一些最常用的~
1、update
intupdate(MappedStatementms,Objectparameter)throwsSQLException;
該方法會(huì)在所有的INSERT、UPDATE、DELETE執(zhí)行時(shí)被調(diào)用,因此如果想要攔截這類操作,可以攔截該方法。接口方法對(duì)應(yīng)的簽名如下。
@Intercepts({
@Signature(type=Executor.class,method="update",args={MappedStatement.class,Object.class})
})
2、query
EListEquery(MappedStatementms,Objectparameter,RowBoundsrowBounds,ResultHandlerresultHandler,CacheKeycacheKey,BoundSqlboundSql)throwsSQLException;
EListEquery(MappedStatementms,Objectparameter,RowBoundsrowBounds,ResultHandlerresultHandler)throwsSQLException;
該方法會(huì)在所有SELECT查詢方法執(zhí)行時(shí)被調(diào)用通過這個(gè)接口參數(shù)可以獲取很多有用的信息,這也是最常被攔截的方法。
@Intercepts({@Signature(
type=Executor.class,
method="query",
args={MappedStatement.class,Object.class,RowBounds.class,ResultHandler.class}
),@Signature(
type=Executor.class,
method="query",
args={MappedStatement.class,Object.class,RowBounds.class,ResultHandler.class,CacheKey.class,BoundSql.class}
)})
3、queryCursor:
ECursorEqueryCursor(MappedStatementms,Objectparameter,RowBoundsrowBounds)throwsSQLException;
該方法只有在查詢的返回值類型為Cursor時(shí)被調(diào)用。接口方法對(duì)應(yīng)的簽名類似于之前的。
//該方法只在通過SqlSession方法調(diào)用commit方法時(shí)才被調(diào)用
voidcommit(booleanrequired)throwsSQLException;
//該方法只在通過SqlSessio口方法調(diào)用rollback方法時(shí)才被調(diào)用
voidrollback(booleanrequired)throwsSQLException;
//該方法只在通過SqlSession方法獲取數(shù)據(jù)庫(kù)連接時(shí)才被調(diào)用,
TransactiongetTransaction();
//該方法只在延遲加載獲取新的Executor后才會(huì)被執(zhí)行
voidclose(booleanforceRollback);
//該方法只在延遲加載執(zhí)行查詢方法前被執(zhí)行
booleanisClosed();
注解的編寫方法都是類似的。
ParameterHandler接口
publicinterfaceParameterHandler{
//該方法只在執(zhí)行存儲(chǔ)過程處理出參的時(shí)候被調(diào)用
ObjectgetParameterObject();
//該方法在所有數(shù)據(jù)庫(kù)方法設(shè)置SQL參數(shù)時(shí)被調(diào)用。
voidsetParameters(PreparedStatementps)throwsSQLException;
}
我都寫一塊啦,如果要攔截某一個(gè)的話只寫一個(gè)即可
@Intercepts({
@Signature(type=ParameterHandler.class,method="getParameterObject",args={}),
@Signature(type=ParameterHandler.class,method="setParameters",args={PreparedStatement.class})
})
ResultSetHandler接口
publicinterfaceResultSetHandler{
//該方法會(huì)在除存儲(chǔ)過程及返回值類型為Cursor以外的查詢方法中被調(diào)用。
EListEhandleResultSets(Statementstmt)throwsSQLException;
//只會(huì)在返回值類型為ursor查詢方法中被調(diào)用
ECursorEhandleCursorResultSets(Statementstmt)throwsSQLException;
//只在使用存儲(chǔ)過程處理出參時(shí)被調(diào)用,
voidhandleOutputParameters(CallableStatementcs)throwsSQLException;
}
@Intercepts({
@Signature(type=ResultSetHandler.class,method="handleResultSets",args={Statement.class}),
@Signature(type=ResultSetHandler.class,method="handleCursorResultSets",args={Statement.class}),
@Signature(type=ResultSetHandler.class,method="handleOutputParameters",args={CallableStatement.class})
})
StatementHandler接口
publicinterfaceStatementHandler{
//該方法會(huì)在數(shù)據(jù)庫(kù)執(zhí)行前被調(diào)用優(yōu)先于當(dāng)前接口中的其他方法而被執(zhí)行
Statementprepare(Connectionconnection,
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請(qǐng)下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請(qǐng)聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會(huì)有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
- 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫(kù)網(wǎng)僅提供信息存儲(chǔ)空間,僅對(duì)用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對(duì)用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對(duì)任何下載內(nèi)容負(fù)責(zé)。
- 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請(qǐng)與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶因使用這些下載資源對(duì)自己和他人造成任何形式的傷害或損失。
最新文檔
- 2025年空乘專業(yè)實(shí)習(xí)總結(jié)模版
- 《護(hù)理教育學(xué)輔導(dǎo)》課件
- 銀行新質(zhì)生產(chǎn)力
- 便便的秘密中班課件
- 年11月音樂教學(xué)工作總結(jié)模版
- 門診部護(hù)理工作總結(jié)模版
- 《基層呼吸系統(tǒng)疾病》課件
- c# 使用計(jì)時(shí)器和觀察者模式實(shí)現(xiàn)報(bào)警推送需求
- bizsim比賽總結(jié)模版
- 2025物業(yè)租賃意向合同
- 數(shù)字貿(mào)易學(xué) 課件 第5章 數(shù)字服務(wù)貿(mào)易
- DB11∕T 848-2023 壓型金屬板屋面工程施工質(zhì)量驗(yàn)收標(biāo)準(zhǔn)
- 孕婦乳母的飲食調(diào)理
- 2023年國(guó)家衛(wèi)生健康委統(tǒng)計(jì)信息中心招聘3人筆試《行政職業(yè)能力測(cè)驗(yàn)》模擬試卷答案詳解版
- 奶茶分析報(bào)告
- 小學(xué)生反詐知識(shí)宣傳課件
- 2023肝硬化腹水診療指南(完整版)
- 顏真卿《勸學(xué)》ppt課件1
- 氫氣儲(chǔ)存和運(yùn)輸 課件 第1、2章 氫氣存儲(chǔ)與運(yùn)輸概述、高壓氣態(tài)儲(chǔ)運(yùn)氫
- 護(hù)士執(zhí)業(yè)注冊(cè)培訓(xùn)合格證明
- 康復(fù)評(píng)定學(xué)第三章-心肺功能評(píng)定課件
評(píng)論
0/150
提交評(píng)論