版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
1、Hibernate梁建全:liangjqday80(2013-11-26)一、什么是Hibernate及其作用Hibernate是一个数据库操作的框架,可以简化数据库操作代码,提升开发效率。(*)Hibernate框架是对Jdbc技术的一个封装。(*)原有的Jdbc技术在操作数据时,有以下几点不足:(1) 需要编写复杂的Sql语句。(2) 需要编写大量的代码实现实体对象和表记录之间的转换。(3) 数据库移植性较差。Hibernate框架可以解决上述Jdbc不足,有以下优点:(*)(1) 可以自动生成SQL(2) 可以自动完成实体类和表记录之间的转换(映射)(3) 可以增强数据库的移植性二、Hi
2、bernate设计原理Hibernate框架是基于ORM思想对Jdbc进行封装设计的。(*)ORM(Object Relation Mapping)被称为对象关系映射。主要思想是:能够完成程序中Java实体对象和关系数据库中表记录之间的转换。开发者可以将对象直接写入数据库,查询时可以直接从数据库中以对象的形式取出结果。中间对象和记录的转换细节由ORM框架负责,开发者对底层细节不用关心。(*)目前流行的ORM工具有以下几个:(1) Hibernate (主流的)(2) MyBatis(IBatis)(3) JPA(4) Toplink三、Hibernate框架结构Hibernate框架使用时,需
3、要以下几个重要文件:(1) 实体类(*.java,n个)(2) hibernate.cfg.xml(仅1个)(src下)Hibernate框架的主配置文件,主要定义数据库和框架的一些连接参数。(3) *.hbm.xml(n个)Hibernate框架的映射描述文件,主要描述实体类和数据表,类属性和表字段之间的对应关系。(4) Hibernate 编程APIa. Configuration -负责加载hibernate.cfg.xml配置文件b. SessionFactory-负责生成数据库的连接对象(Session)c. Session -代指程序与数据库的连接,用于执行增、删、改、查基本操作d
4、. Query-用于执行非主键查询的操作(HQL语句)e. Transaction -用于事务控制,将两个或两个以上DML操作封装成一个整体操作。四、Hibernate基本应用(*)1:使用步骤step1,引入hibernate框架开发包、数据库驱动包step2,引入hibernate.cfg.xml配置文件step3,根据数据表编写一个实体类step4,编写hbm.xml映射描述文件(描述实体类和表之间的对应关系)step5,使用Hibernate API编程/按主键作条件查询session.load();session.get();/添加记录-插入session.save(obj);-主键
5、值生成方式在hbm.xml映射描述部分设置-进行DML操作,必须显示追加事务commit/更新记录-更新1行(按主键做条件)session.update(obj);-obj需要通过session.get/load查询,不推荐new方式,因为会将未设置属性值的字段清空/删除记录-删除1行(按主键做条件)session.delete(obj);-obj可以通过session.get/load查询,也可以new指定id值。五、Hibernate映射类型在hbm.xml中定义属性和字段映射时,通过type属性指定映射类型,可以采用下面两种方法指定:1:指定Java类型 java.lang.String
6、 java.lang.Integer2:Hibernate映射类型(推荐) 字符串:String 整数:byte,short,integer,long 浮点数:float,double 日期:date,time,timestamp boolean类型: true_false : 完成实体类boolean属性和表字段char之间的转换。 true值转换成T; false值转换成F yes_no : 完成实体类boolean属性和表字段char之间的转换。 true值转换成Y; false值转换成N=create table t_foo( id number(9) primary key, nam
7、e varchar2(20), birth date,login_time timestamp, marry char(1);create table t_foo( id int primary key auto_increment, name varchar(30), birth date, login_time timestamp, marry char(1) ) default charset=utf8;day81(2013-11-27)一、主键生成方法(Hibernate对主键的管理)在hbm.xml映射描述中,可以指定主键值采用哪种方法生成和管理。(仅适用于添加操作)<gene
8、rator class=“生成方法”>/.</generator>class属性用于指定主键生成方法,Hibernate提供了以下几个预定义的方法:1) sequence -采用一个序列生成主键值(只用于Oracle) <generator class=“sequence”><param name=”sequence”>指定序列名称(默认名:hibernate_sequence)</param> </generator>2) identity -Hibernate会利用数据库自动增长机制生成主键(mysql, sqlserver
9、)<generator class=“identity”> </generator> 注意:建表时需要为主键字段设置自增长功能!3) native -根据hibernate.cfg.xml中的dialect属性指定主键生成方法。如果dialect是OracleDialecct会采用sequence方法;如果是MySQLDialect会采用identity方法。4) increment(适用于各种数据库)-首先发送一个select max(ID)语句查询当前表中ID最大值,然后将最大值+1给insert语句指定。 <generator class=“incremen
10、t”> </generator> 注意:该方式在并发时,有可能产生重复ID,因此并发几率高时,不要使用。5) assigned-Hibernate会放弃主键值的生成和管理。意味着程序员需要在程序中显式指定ID值。6) hilo、uuid等算法 hilo:采用高低位算法生成一个主键值 uuid:采用UUID算法生成一个主键值(字符串类型的ID)<generator class=“uuid”> </generator>注意:id的字段值用varchar()二、Hibernate基本特性:一级缓存(以内存空间换取时间的一种策略)(*)什么是一级缓存sessi
11、on.get/load方法时,会先去一级缓存查找,没有对象信息才去数据库查找,查找后将返回的对象放入一级缓存。后续再查找该对象会返回缓存中的信息,从而减少了访问数据库的次数。一级缓存是属于Session级别的缓存,由Session对象负责管理。不同的Session对象有不同的缓存空间,不能互相访问。2)一级缓存的好处利用同一个Session多次访问同一个实体对象时,对数据库只查访一次,后续几次从缓存获取。3)一级缓存的管理session.evict(obj);/将obj对象从一级缓存移出session.clear(); /清空一级缓存的所有对象session.close(); / 关闭连接,释
12、放缓存资源=/循环次数很多,每次id不同时for() Cost cost = (Cost) session.get(Cost.class, id); /使用cost对象-省略 session.evict(cost);/及时清理缓存的对象=:对象持久化1)什么是持久化Hibernate的持久化指的是将程序中Java对象的数据以数据库存储形式保存下来。Hibernate是一个持久层框架。持久层里面都是由持久对象构成,这些对象的持久化操作由Hibernate实现。对象持久性:当一个对象的数据发生改变,会与数据库记录进行同步修改。2)对象的状态hibernate中对象可以有以下3种状态:a 临时状态(
13、临时对象) 调用new方式创建的实体类对象,未经过session处理的。b 持久状态(持久对象) 实体类对象与session发生关联。通过session的save()、update()、get() load()方法处理后,该对象与session发生关联。 -具有持久性的对象都是在一级缓存中存放的-持久对象不能被垃圾回收器回收,它的数据状态改变可以与数据库同步,由session负责同步操作-持久对象数据改变后,在事务commit之后执行update更新。(游离对象)持久对象经过以下方法处理后会变成游离状态。session的clear()、evict()、close()方法。使用上述方法,将对象从
14、缓存中清除出去,因此丧失了持久性。session.flush();/将缓存中对象与数据库同步 mit();/等价于session.flush+事务提交 c 游离状态(游离对象) 持久对象经过以下方法处理后会变成游离状态.session的clear()、evict()、close()方法 使用上述方法,将对象从缓存中清除出去,因此丧失了持久性。=/向COST插入100000条记录Transaction tx = session.beginTransaction();/插入操作for(int i=1;i<=100000;i+) Cost cost = new Cost(); /设置cost属
15、性值 session.save(cost); /分批执行 if(i%100 = 0) /将缓存对象与数据库同步操作 session.flush(); session.clear();/清除缓存的对象 mit();session.close();3:延迟加载(*)a. 什么是延迟加载当使用了延迟加载操作的方法后,再查询时并没有立刻返回数据库中的数据信息,而是在调用实体对象的getXXX方法时才会发送SQL语句加载数据库数据。 b. 哪些操作会采用延迟加载机制查询:load()延迟加载;get()非延迟加载执行HQL: iterator()延迟加载;list()非延迟加载 关联操作:获取关联对象属
16、性值时,采用的延迟加载机制c. 使用延迟加载机制需要注意的问题org.hibernate.LazyInitializationException:could not initialize proxy - no Session上述异常原因是:采用了延迟加载的方法,但是将session关闭过早(在加载数据前关闭),导致的错误。 解决方法如下: 1)放弃延迟加载方法,采用立刻加载方法 2)坚持使用延迟加载方法,将session关闭推迟到数据加载后 在项目中如果使用了延迟加载方法,一般会采用OpenSessionInView思想控制Session关闭。思想:在JSP标签解析之前不关闭Session,而
17、是在JSP解析之后关闭Session.实现方案如下: -采用Struts2的Interceptor拦截器 -采用Servlet中的Filter过滤器day82(2013-11-28)一、延迟加载优势提高内存的使用率,减少对象占用内存时间减少用户并发几率二、在项目中采用延迟加载操作一般采取OpenSessionInview模式控制(避免Session关闭过早导致延迟实例化异常)-采用Struts2的Interceptor拦截器-采用Servlet中的Filter过滤器public class MyInterceptor implements Interceptor public String i
18、ntercept(ActionInvocation in) /追加前期处理逻辑 in.invoke();/调用后续的Action,Result(JSP)组件 /追加后期处理逻辑,session.close() *.action->Struts2控制器(struts.xml)->拦截器(前期处理)->Action,DAO.findById(session.load()->Result(JSP)通过解析JSP标签和表达式(触发getXXX),加载数据库数据,显示->拦截器(后期处理,session.close()->给浏览器响应JSP生成的内容public cl
19、ass MyFilter implements Filter public void doFilter(request,response,chain) /前期处理逻辑 chain.doFilter(request,response);/调用后续的JSP,Servelt等组件处理 /后期处理逻辑session.close(); day83(2013-11-28)*三、案例(重构原项目中资费管理模块功能) 重构步骤如下: 1)引入框架开发包和驱动包 2)src添加hibernate.cfg.xml配置,参数与原perties一致3)添加Cost.hbm.xml描述文件 (注意检查hbm
20、.xml文件中的包名.类名是否与工程中Cost实体类一致,属性名是否与Cost定义一致) 4)在hibernate.cfg.xml中利用<mapping>引入Cost.hbm.xml -以上为准备工作- 5)编写HibernateUtil类封装session获取 6)按原有的ICostDAO接口编写Hibernate实现的DAO 7)测试Hibernate的DAO实现(建议) 8)修改DAOFactory将Hibernate实现的DAO装配上,将来给Action传入 9)与界面结合集成测试 -采用OpenSessionInView模式控制Session关闭- 1)修改Hiberna
21、teUtil工具类,采用ThreadLocal封装Session 2)编写一个Interceptor,在in.invoke()方法后关闭Session 取消原DAO中的session.close()3)在struts配置中定义Interceptor组件,为某个<action>添加引入时,需要追加defaultStack的引用.<action name="" class=""> <interceptor-ref name="opensession"/> <interceptor-ref name
22、="defaultStack"/> .</action>四、延迟加载实现原理(了解)采用了动态代理技术实现延迟加载机制。(主要基于cglib.jar实现) (Hibernate在底层动态的生成了一个新的实体类) 采用延迟加载方法,例如load,Hibernate返回的是一个动态生成的实体类对象,该对象类型是原实体类的子类。在动态生成的实体类中将原实体类属性的get方法进行了重写。 public class Cost$EnhancerByCGLIB$sew34() extends Cost public String getXXX() /判断当前对象的各个属
23、性是否已加载DB数据 /如果未加载,发送SQL语句实例化 /如果已加载,调用super.getName();返回属性值 Cost cost = new Cost$EnhancerByCGLIB$sew34();*五、session.get()和session.load()的区别和联系 -两个方法都是按主键做条件查询 -load采用延迟加载机制; get采用立刻加载机制 -load方法返回一个动态代理类AutoProxy对象; get方法返回原有实体类对象-get未查询出记录返回null; load为查询出记录采取抛出ObjectNotFoundException六、Hibernate关联映射
24、ACCOUNT:账务账号 SERVICE:业务账号 联条件:ACCOUNT.ID=SERVICE.ACCOUNT_ID 一个账务账号可以对应多个业务账号 一个业务账号可以对应一个账号账号关联映射好处ACCOUNT->Account SERVICE->Service 在实体对象间如果建立了关联映射,可以通过Account对象获取相关的Service对象。也可以通过Service获取相关的Account对象。 基于关联映射实现根据Account寻找Service信息,操作步骤-在Account方添加一个Set集合属性,用于存储相关的多个Service对象信息. 添加setter和get
25、ter方法 -在Account.hbm.xml文件中描述关联属性(Set集合属性) <set name="集合属性名"> <key column="指定关联条件中的外键字段名"> </key> <one-to-many class="指定要关联和加载的一方Service"/> </set> -使用方法 account.getServices();获取当前account对象相关的Service对象信息 注意:关联属性数据加载默认采用延迟加载机制,使用中不要过早关闭session
26、。day84(2013-11-29)一、多对一的关系映射many-to-one案例:基于多对一关系实现根据Service获取相关的Account信息-在Service实体类中添加一个Account类型属性,用于存储相关的Account对象 -在Service.hbm.xml中采用<many-to-one>描述关联属性 <many-to-one name="关联属性名" class="要关联和加载的一方Account" column="指定关联条件的外键字段"/> -删除Service.hbm.xml中与colu
27、mn重复的字段映射<property> -删除Service实体类中的accountId属性 -通过service.getAccount()就可以获取相关的Account信息.二、基于关系的操作 1) 关联抓取join fetch(*) 默认情况下,关联属性在抓取时,采用单独在发送一条SQL语句实现。 采用join fetch可以实现用一条SQL语句抓取主对象和关联属性的数据信息。 HQL语法格式: "from 类型 别名 join fetch 别名.关联属性" "from Account a join fetch a.services s"
28、提示:如果需要使用主对象和关系属性数据,建议采用join fetch方式,可以减少与数据库的交互次数。2) 在关系属性映射部分使用lazy="false" 和fetch="select"等价于join fetch的作用。 lazy属性可以控制是否延迟加载. lazy="true"采取延迟加载 lazy="false"采取立刻加载 fetch属性可以控制关联属性抓取的策略 fetch="select"单独再发送一个select语句加载关联属性数据(默认值) fetch="join&quo
29、t;采取表连接方式将关联属性数据同主对象一同抓取.(一个SQL完成) 提示:该方法不推荐采用,因为会影响所有查询操作,建议采用HQL的join fetch写法。Service s = (Service) session.load(Service.class ,2001); s.getId();/不查询dbs.getOsUsername();/默认触发SERVICE单表查询。 如果hbm.xml中account映射部分加了lazy=false, fetch=join,会去查询SERVICE和ACCOUNT两个表加载数据3) 级联的DML操作 在建立关联映射之后,可以通过关系实现级联的添加、删除、
30、更新操作。 a .级联添加 在对主对象进行添加时,可以对主对象包含的关联属性的数据也进行添加。 -将Account对象的services关系属性赋值 account.getServices().add(s1); account.getServices().add(s2); -需要在services映射部分添加cascade属性设置,该属性值如下: none(默认):不支持级联操作 save-update:支持级联添加和更新 delete:支持级联的删除 all:支持级联的添加、更新、删除 -执行session.save(account) 对ACCOUNT添加一行记录,并向SERVICE添加se
31、rvices属性中的记录4) inverse="true"属性设置 默认情况下,在进行级联操作时,Hibernate除了对关联属性数据进行级联的添加、更新操作之外,还需要将关系属性的记录与当前主对象记录的关系字段进行维护更新(即更新关联条件的外键字段值update)。inverse="true"意思是在执行级联操作时,放弃关系维护工作(即不执行update语句)使用建议:在one-to-many映射一方添加inverse="true",可以避免update关系字段的操作。 5) 级联删除 当对主对象进行删除时,关联属性的记录也进行相应
32、删除。 -session.delete(account);account需要采用查询方式获取,不能采用new方式。 -在关联属性映射部分设置cascade属性,支持级联删除操作。-建议在one-to-many部分添加inverse=true设置,这样可以避免update account_id=null的操作,直接进行delete删除。三、多对多关系数据库设计时,表数据如果有多对多关系,需要采用3张数据表存储。例如 ADMIN_INFO 管理员信息表 ADMIN_ROLE关系表 ROLE_INFO角色信息表 一个管理员可以有多个角色 一个角色可以分配给多个管理员 采用Hibernate实现根据A
33、dmin查找相关Role信息.具体操作如下: 需要建立Admin->Role多对多关系映射-在Admin类中添加Set<Role>集合关系属性-在Admin.hbm.xml中添加关系属性的描述<set name="关系属性名" table="中间的关系表"> <key column="关系表中与当前方关联的字段名"></key> <many-to-many class="要关联和加载的一方" column="关系表中与class方关联的字段名&q
34、uot;> </many-to-many></set>-使用admin.getRoles()方法可以获取当前Admin对象相关的Role对象信息。-admin.getRoles().add(role);/追加关系-admin.getRoles().remove(role);/移除关系day85(2013-11-30)四、继承关系映射一、Hibernate查询(HQL)(*)-day01-1.了解Hibernate框架的作用和实现原理*2.掌握单表映射及其基本增删改查操作-day02-1.什么是一级缓存、延迟加载、对象持久性*2掌握一级缓存、延迟加载编程注意事项 -
35、注意避免缓存溢出,适当采用缓存清理的方法 -采用延迟加载避免session.close过早。 OpenSessionInView方式控制。案例:完成资费重构,支持延迟加载操作-day03-day04-*1.掌握关联映射的建立方法 一对多、多对一、多对多、继承2.(更高要求)基于关系进行抓取和级联操作-day05-*1.掌握Hibernate查询(HQL)2.了解Hibernate高级特性 二级缓存、查询缓存、事务并发处理=1.继承关系映射设计特点:(父类一张表、每个子类各自一张表),采用<joined-subclass>描述继承关系 a.PRODUCT表ID,NAME,PRICE,
36、DESCR,PIC b.BOOK表ID,AUTHOR,PUBLISHING,ISBN, PUBLISHTIME c.CAR表ID,BRAND,COLOR,TYPE-create table PRODUCT( id number(9) primary key, name varchar2(20), price number(11,2), descr varchar2(2000), pic varchar2(100);create table BOOK( id number(9) primary key, author varchar2(20), publishing varchar2(100),
37、 isbn varchar2(50), publish_time date);create table CAR(id number(9) primary key,brand varchar2(50),color varchar2(10),car_type varchar2(10);-继承描述规则-<joined-subclass name="指定子类类型" table="指定子类表" extends="指定父类类型"> <key column="指定子类表与父类表关联的字段"> </k
38、ey>/定义子类属性和子表映射描述<property></joined-subclass>二、Hibernate高级特性(了解)1:二级缓存二级缓存默认关闭,需要时需要利用配置开启。二级缓存可以给各个Session共享,进行访问。二级缓存与SessionFactory相关,属于SessionFactory级别。二级缓存组件可以利用以下第三方实现:ehcache.jarswarmcache.jaroscache.jar二级缓存的使用方法如下:step1,引入二级缓存的组件及其配置文件step2,在hibernate.cfg.xml中设置开启,并指定使用上述组件应用s
39、tep3,在需要应用二级缓存的实体类.hbm.xml文件中,追加<cache>配置二级缓存适用情况:1.存储的是单个对象,如果该对象需要被多个用户和请求访问,适合使用2.对象数据状态改变的频率越低越好二级缓存的管理方法:1.sessionFactory.evict(Class c); 清除class类型的所有对象2. sessionFactory.evict(Class c,id); 清除指定id的class对象2:查询缓存一级和二级缓存只能存储单个实体对象,如果需要存储结果集、字符串数组等信息。可以采用查询缓存实现。查询缓存实际上是借助二级缓存空间存储,可以存储执行SQL和结果内
40、容。查询缓存的使用方法:step1,对要查询的对象开启二级缓存功能step2,在hibernate.cfg.xml配置中开启查询缓存设置step3,在执行query.list()查询前,设置query.setCacheable(true);表明对该查询 采用查询缓存机制。3:事务并发处理当遇到对某个记录同时操作时,可能会产生事务并发现象。Hibernate提供了两种锁机制,处理这种事务并发问题。1) 悲观锁悲观锁机制,当一个事务操作某条记录时,Hbiernate将这条记录锁定,之后其他事务不能对该记录进行任何操作。使用方法如下:只要在查询时,追加锁定参数即可。session.load(Trai
41、n.class, 1, LockMode.UPGRADE);上述代码可以将id=1的记录锁定,等当前线程执行了事务的commit或rollback,其他线程才以查询和更新等操作。2)乐观锁乐观锁机制:允许多个线程同时对某一条记录进行查询和更新,但是只能有一个人成功。第一个最先提交事务的人成功,其他人失败。乐观锁使用方法如下:step1,向表中追加一列字段,数值类型,用于充当版本字段step2,在实体类和hbm.xml中定义该版本字段实体类追加一个属性,hbm.xml追加<version>元素定义step3,在hbm.xml的class元素追加optimistic-lock=”ver
42、sion”属性(默认就是此值)含义:采用版本字段值解决事务并发冲突。当第一个人提交更新之后,会自动将版本字段值加1处理。其他人提交时如果版本属性小于数据库中版本字段值,就认为失败,抛出异常处理。3)乐观锁和悲观锁使用选择悲观锁产生并发的事务都可以依次处理成功。乐观锁产生并发的事务排他性,一个成功,其他失败。悲观锁依次处理各个请求,需要的处理时间较长,效率低。乐观锁采用抢占式,处理大量并发时,效率高。四、其他1)Log4j工具 Log4j是一个日志工具。主要用于消息输出。 结构主要有以下3部分: -Logger日志器 负责消息输出,提供了各种消息输出方法。 -Appender输出器 负责消息采用
43、哪种方式输出。例如控制台输出、文件输出等 -Layout布局器 负责消息内容输出格式。 上述三部分组件,一个Logger可以对应多个Appender组件,每个Appender可以对应一个Layout. Logger输出消息级别为调试、普通、警告、错误、致命等5个级别。 使用方法如下: -引入log4j.jar开发包 -在src下添加perties 2)联合主键映射a.联合主键实体类: 由实体类Person和主键类PersonId(必须实现序列化接口)构成.b.hbm.xml描述文件采用<composite-id>元素定义主键描述,不能使用主键自动生成策略。添加时需要在程序中自己执行主键值。利用MyEclipse工具添加Hibernate框架,步骤如下: a. 创建工程,工程右键->MyEclipse->Add Hibernate Capabilities. b.在弹出的向导页面,第一个页面选择要添加的Hi
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 2025年氢燃料电池测试设备验证方案
- 智慧茶园自动采摘机租赁管理服务续费管理2025年的合同协议
- 2025年国际汉语教师证书考试汉语教学与文化教学融合试卷及答案
- 记账实操-小企业成本核算方法
- 护理与医院文化建设
- 2026年电子元器件制造外包合同
- 排泄护理的继续教育
- 护理课件模板下载分享
- 母婴护理教学背景图
- 2025年元宇宙场景架构师的技术培训课程设计与教学方法
- 矛盾纠纷调解培训课件
- 尿路梗阻的健康宣教
- 【MOOC】创业风险识别与规避-中南财经政法大学 中国大学慕课MOOC答案
- 中央空调系统维保服务报价清单
- 《汉书》导读学习通超星期末考试答案章节答案2024年
- 金版教程物理2024导学案必修第册人教版新第十章 静电场中的能量第十章 知识网络构建含答案
- 急性呼吸窘迫综合征-课件
- NITON-XL3t(美国力通-矿石元素分析仪)用户手册-中文
- DL∕T 1952-2018 变压器绕组变形测试仪校准规范
- 华南理工综评机测试题(一)
- 全国专利代理师资格考试专利法律知识专项考试试题
评论
0/150
提交评论