EJB之JPA(事务回滚)

EJB之JPA(事务回滚),第1张

;   package leadfar jpa;import java util Random;import javax naming InitialContext;import javax transaction UserTransaction;import leadfar jpa StudentManager;import junit framework TestCase;public class StudentManagerClient extends TestCase{        public void testAddStudent () throws Exception{        InitialContext context = new InitialContext()         StudentManager = (StudentManager)context lookup( StudentManager/remote )         for(int i= ; i< ; i++){        addStudent( 学生 +new Random() nextInt( ))         if(i > ){        throw new RuntimeException( 异常! )         //数据库依然会 去数据         }        }        }        public void testAddStudent () throws Exception{        InitialContext context = new InitialContext()         StudentManager = (StudentManager)context lookup( StudentManager/remote )         //JTA的事务管理由UserTransaction管理         UserTransaction utx = (UserTransaction)context lookup( UserTransaction )         try{        utx begin()         //没有数据会插入数据库 整个事务都回滚         for(int i= ; i< ; i++){        addStudent( 学生 +new Random() nextInt( ))         if(i > ){        throw new RuntimeException( 异常! )         }        }        mit()         }catch(Exception e){        utx rollback()         e printStackTrace()         }        }        } lishixinzhi/Article/program/Java/hx/201311/27051

写个适配器器吧,传入数据库类型,输出拼接的sql。

其实个人更建议你使用hibernate的Hql来作sql相关 *** 作,首先它语法和通用Sql是近似的,不一样的地方在于 语句里写的是类名 不是表名,最后hibernate会根据Hql翻译成当前数据库可执行的sql来执行,这样你就不需要去判断究竟是什么数据库,写法支持不支持。

当然,如果你的语句特复杂一定得用Sql server或者Oracle的特殊语法,那没办法,写个适配器吧

以下是HQL相关资料

HQL查询:

Criteria查询对查询条件进行了面向对象封装,符合编程人员的思维方式,不过HQL(Hibernate Query Lanaguage)查询提供了更加丰富的和灵活的查询特性,因此

Hibernate将HQL查询方式立为官方推荐的标准查询方式,HQL查询在涵盖Criteria查询的所有功能的前提下,提供了类似标准SQL语句的查询方式,同时也提供了更

加面向对象的封装。完整的HQL语句形势如下:

Select/update/delete…… from …… where …… group by …… having …… order by …… asc/desc

其中的update/delete为Hibernate3中所新添加的功能,可见HQL查询非常类似于标准SQL查询。由于HQL查询在整个Hibernate实体 *** 作体系中的核心地位,这一节我

将专门围绕HQL *** 作的具体技术细节进行讲解。

1、 实体查询:

有关实体查询技术,其实我们在先前已经有多次涉及,比如下面的例子:

String hql=”from User user ”;

List list=sessionCreateQuery(hql)list();

上面的代码执行结果是,查询出User实体对象所对应的所有数据,而且将数据封装成User实体对象,并且放入List中返回。这里需要注意的是,Hibernate的实体查

询存在着对继承关系的判定,比如我们前面讨论映射实体继承关系中的Employee实体对象,它有两个子类分别是HourlyEmployee,SalariedEmployee,如果有这样的

HQL语句:“from Employee”,当执行检索时Hibernate会检索出所有Employee类型实体对象所对应的数据(包括它的子类HourlyEmployee,SalariedEmployee对应

的数据)。

因为HQL语句与标准SQL语句相似,所以我们也可以在HQL语句中使用where字句,并且可以在where字句中使用各种表达式,比较 *** 作符以及使用“and”,”or”连接

不同的查询条件的组合。看下面的一些简单的例子:

from User user where userage=20;

from User user where userage between 20 and 30;

from User user where userage in(20,30);

from User user where username is null;

from User user where username like ‘%zx%’;

from User user where (userage%2)=1;

from User user where userage=20 and username like ‘%zx%’;

2、 实体的更新和删除:

在继续讲解HQL其他更为强大的查询功能前,我们先来讲解以下利用HQL进行实体更新和删除的技术。这项技术功能是Hibernate3的新加入的功能,在Hibernate2

中是不具备的。比如在Hibernate2中,如果我们想将数据库中所有18岁的用户的年龄全部改为20岁,那么我们要首先将年龄在18岁的用户检索出来,然后将他们的

年龄修改为20岁,最后调用Sessionupdate()语句进行更新。在Hibernate3中对这个问题提供了更加灵活和更具效率的解决办法,如下面的代码:

Transaction trans=sessionbeginTransaction();

String hql=”update User user set userage=20 where userage=18”;

Query queryupdate=sessioncreateQuery(hql);

int ret=queryupdateexecuteUpdate();

transcommit();

通过这种方式我们可以在Hibernate3中,一次性完成批量数据的更新,对性能的提高是相当的可观。同样也可以通过类似的方式来完成delete *** 作,如下面的代码

Transaction trans=sessionbeginTransaction();

String hql=”delete from User user where userage=18”;

Query queryupdate=sessioncreateQuery(hql);

int ret=queryupdateexecuteUpdate();

transcommit();

如果你是逐个章节阅读的化,那么你一定会记起我在第二部分中有关批量数据 *** 作的相关论述中,讨论过这种 *** 作方式,这种 *** 作方式在Hibernate3中称为bulk

delete/update,这种方式能够在很大程度上提高 *** 作的灵活性和运行效率,但是采用这种方式极有可能引起缓存同步上的问题(请参考相关论述)。

3、 属性查询:

很多时候我们在检索数据时,并不需要获得实体对象所对应的全部数据,而只需要检索实体对象的部分属性所对应的数据。这时候就可以利用HQL属性查询技术

,如下面程序示例:

List list=sessioncreateQuery(“select username from User user ”)list();

for(int i=0;i<listsize();i++){

Systemoutprintln(listget(i));

}

我们只检索了User实体的name属性对应的数据,此时返回的包含结果集的list中每个条目都是String类型的name属性对应的数据。我们也可以一次检索多个属性,

如下面程序:

List list=sessioncreateQuery(“select username,userage from User user ”)list();

for(int i=0;i<listsize();i++){

Object[] obj=(Object[])listget(i);

Systemoutprintln(obj[0]);

Systemoutprintln(obj[1]);

}

此时返回的结果集list中,所包含的每个条目都是一个Object[]类型,其中包含对应的属性数据值。作为当今我们这一代深受面向对象思想影响的开发人员,可能

会觉得上面返回Object[]不够符合面向对象风格,这时我们可以利用HQL提供的动态构造实例的功能对这些平面数据进行封装,如下面的程序代码:

List list=sessioncreateQuery(“select new User(username,userage) from User user ”)list();

for(int i=0;i<listsize();i++){

User user=(User)listget(i);

Systemoutprintln(usergetName());

Systemoutprintln(usergetAge());

}

这里我们通过动态构造实例对象,对返回结果进行了封装,使我们的程序更加符合面向对象风格,但是这里有一个问题必须注意,那就是这时所返回的User对象,

仅仅只是一个普通的Java对象而以,除了查询结果值之外,其它的属性值都为null(包括主键值id),也就是说不能通过Session对象对此对象执行持久化的更新 ***

作。如下面的代码:

List list=sessioncreateQuery(“select new User(username,userage) from User user ”)list();

for(int i=0;i<listsize();i++){

User user=(User)listget(i);

usersetName(“gam”);

sessionsaveOrUpdate(user);//这里将会实际执行一个save *** 作,而不会执行update *** 作,因为这个User对象的id属性为null,Hibernate会把它作为一个自由对

象(请参考持久化对象状态部分的论述),因此会对它执行save *** 作。

}

4、 分组与排序

A、Order by子句:

与SQL语句相似,HQL查询也可以通过order by子句对查询结果集进行排序,并且可以通过asc或者desc关键字指定排序方式,如下面的代码:

from User user order by username asc,userage desc;

上面HQL查询语句,会以name属性进行升序排序,以age属性进行降序排序,而且与SQL语句一样,默认的排序方式为asc,即升序排序。

B、Group by子句与统计查询:

在HQL语句中同样支持使用group by子句分组查询,还支持group by子句结合聚集函数的分组统计查询,大部分标准的SQL聚集函数都可以在HQL语句中使用,比如:

count(),sum(),max(),min(),avg()等。如下面的程序代码:

String hql=”select count(user),userage from User user group by userage having count(user)>10 ”;

List list=sessioncreateQuery(hql)list();

C、优化统计查询:

假设我们现在有两张数据库表,分别是customer表和order表,它们的结构如下:

customer

ID varchar2(14)

age number(10)

name varchar2(20)

order

ID varchar2(14)

order_number number(10)

customer_ID varchar2(14)

现在有两条HQL查询语句,分别如下:

from Customer c inner join corders o group by cage;(1)

select cID,cname,cage,oID,oorder_number,ocustomer_ID

from Customer c inner join corders c group by cage;(2)

这两条语句使用了HQL语句的内连接查询(我们将在HQL语句的连接查询部分专门讨论),现在我们可以看出这两条查询语句最后所返回的结果是一样的,但是它们

其实是有明显区别的,语句(1)检索的结果会返回Customer与Order持久化对象,而且它们会被置于Hibernate的Session缓存之中,并且Session会负责它们在缓存

中的唯一性以及与后台数据库数据的同步,只有事务提交后它们才会从缓存中被清除;而语句(2)返回的是关系数据而并非是持久化对象,因此它们不会占用

Hibernate的Session缓存,只要在检索之后应用程序不在访问它们,它们所占用的内存就有可能被JVM的垃圾回收器回收,而且Hibernate不会同步对它们的修改。

在我们的系统开发中,尤其是Mis系统,不可避免的要进行统计查询的开发,这类功能有两个特点:第一数据量大;第二一般情况下都是只读 *** 作而不会涉及到对统

计数据进行修改,那么如果采用第一种查询方式,必然会导致大量持久化对象位于Hibernate的Session缓存中,而且Hibernate的Session缓存还要负责它们与数据

库数据的同步。而如果采用第二种查询方式,显然就会提高查询性能,因为不需要Hibernate的Session缓存的管理开销,而且只要应用程序不在使用这些数据,它

们所占用的内存空间就会被回收释放。

因此在开发统计查询系统时,尽量使用通过select语句写出需要查询的属性的方式来返回关系数据,而避免使用第一种查询方式返回持久化对象(这种方式是在有

修改需求时使用比较适合),这样可以提高运行效率并且减少内存消耗。㊣真正的高手并不是精通一切,而是精通在合适的场合使用合适的手段。

5、 参数绑定:

Hibernate中对动态查询参数绑定提供了丰富的支持,那么什么是查询参数动态绑定呢?其实如果我们熟悉传统JDBC编程的话,我们就不难理解查询参数动态绑定,

如下代码传统JDBC的参数绑定:

PrepareStatement pre=connectionprepare(“select from User where username=”);

presetString(1,”zhaoxin”);

ResultSet rs=preexecuteQuery();

在Hibernate中也提供了类似这种的查询参数绑定功能,而且在Hibernate中对这个功能还提供了比传统JDBC *** 作丰富的多的特性,在Hibernate中共存在4种参数绑

定的方式,下面我们将分别介绍:

A、 按参数名称绑定:

在HQL语句中定义命名参数要用”:”开头,形式如下:

Query query=sessioncreateQuery(“from User user where username=:customername and usercustomerage=:age ”);

querysetString(“customername”,name);

querysetInteger(“customerage”,age);

上面代码中用:customername和:customerage分别定义了命名参数customername和customerage,然后用Query接口的setXXX()方法设定名参数值,setXXX()方法包

含两个参数,分别是命名参数名称和命名参数实际值。

B、 按参数位置邦定:

在HQL查询语句中用””来定义参数位置,形式如下:

Query query=sessioncreateQuery(“from User user where username= and userage = ”);

querysetString(0,name);

querysetInteger(1,age);

同样使用setXXX()方法设定绑定参数,只不过这时setXXX()方法的第一个参数代表邦定参数在HQL语句中出现的位置编号(由0开始编号),第二个参数仍然代表参

数实际值。

注:在实际开发中,提倡使用按名称邦定命名参数,因为这不但可以提供非常好的程序可读性,而且也提高了程序的易维护性,因为当查询参数的位置发生改变时

,按名称邦定名参数的方式中是不需要调整程序代码的。

C、 setParameter()方法:

在Hibernate的HQL查询中可以通过setParameter()方法邦定任意类型的参数,如下代码:

String hql=”from User user where username=:customername ”;

Query query=sessioncreateQuery(hql);

querysetParameter(“customername”,name,HibernateSTRING);

如上面代码所示,setParameter()方法包含三个参数,分别是命名参数名称,命名参数实际值,以及命名参数映射类型。对于某些参数类型setParameter()方法可

以更具参数值的Java类型,猜测出对应的映射类型,因此这时不需要显示写出映射类型,像上面的例子,可以直接这样写:

querysetParameter(“customername”,name);但是对于一些类型就必须写明映射类型,比如javautilDate类型,因为它会对应Hibernate的多种映射类型,比如

HibernateDATA或者HibernateTIMESTAMP。

D、 setProperties()方法:

在Hibernate中可以使用setProperties()方法,将命名参数与一个对象的属性值绑定在一起,如下程序代码:

Customer customer=new Customer();

customersetName(“pansl”);

customersetAge(80);

Query query=sessioncreateQuery(“from Customer c where cname=:name and cage=:age ”);

querysetProperties(customer);

setProperties()方法会自动将customer对象实例的属性值匹配到命名参数上,但是要求命名参数名称必须要与实体对象相应的属性同名。

这里还有一个特殊的setEntity()方法,它会把命名参数与一个持久化对象相关联,如下面代码所示:

Customer customer=(Customer)sessionload(Customerclass,”1”);

Query query=sessioncreateQuery(“from Order order where ordercustomer=:customer ”);

query setProperties(“customer”,customer);

List list=querylist();

上面的代码会生成类似如下的SQL语句:

Select from order where customer_ID=’1’;

E、 使用绑定参数的优势:

我们为什么要使用绑定命名参数?任何一个事物的存在都是有其价值的,具体到绑定参数对于HQL查询来说,主要有以下两个主要优势:

①、 可以利用数据库实施性能优化,因为对Hibernate来说在底层使用的是PrepareStatement来完成查询,因此对于语法相同参数不同的SQL语句,可

以充分利用预编译SQL语句缓存,从而提升查询效率。

②、 可以防止SQL Injection安全漏洞的产生:

SQL Injection是一种专门针对SQL语句拼装的攻击方式,比如对于我们常见的用户登录,在登录界面上,用户输入用户名和口令,这时登录验证程序可能会生成如

下的HQL语句:

“from User user where username=’”+name+”’ and userpassword=’”+password+”’ ”

这个HQL语句从逻辑上来说是没有任何问题的,这个登录验证功能在一般情况下也是会正确完成的,但是如果在登录时在用户名中输入”zhaoxin or ‘x’=’x”,

这时如果使用简单的HQL语句的字符串拼装,就会生成如下的HQL语句:

“from User user where username=’zhaoxin’ or ‘x’=’x’ and userpassword=’admin’ ”;

显然这条HQL语句的where字句将会永远为真,而使用户口令的作用失去意义,这就是SQL Injection攻击的基本原理。

而使用绑定参数方式,就可以妥善处理这问题,当使用绑定参数时,会得到下面的HQL语句:

from User user where username=’’zhaoxin’’ or ‘’x=’’x’’ ‘ and userpassword=’admin’;由此可见使用绑定参数会将用户名中输入的单引号解

析成字符串(如果想在字符串中包含单引号,应使用重复单引号形式),所以参数绑定能够有效防止SQL Injection安全漏洞。

java编程语言一直以来都作为企业级应用软件开发的主流编程语言。今天,北京java课程就一起来了解一下,在企业级应用编程开发项目中都有哪些框架结构是可以使用的。

软件开发人员通常会面对如下需求组合(至少我经常遇到的是):设计优良的数据存储结构(有时候是已有的旧数据库模型),大量的数据录入表单,非常复杂的业务逻辑、报表功能、与许多公司其他系统(财会、CRM等)集成,数千并发量。对此,你先考虑的是什么

“采用主流的关系数据数据库管理(RDBMS)、Hibernate/JPA+SpringBoot、RESTAPI,并且使用我喜欢的或者新的JS框架来实现UI。”

“嗯~,还需配置SpringSecurity,也许还需要写一部分代码来实现行级别的数据保护功能。如何实现呢也许会用到数据库视图或虚拟专用数据库。“

“所有这些DAO代码都非常相似且枯燥,但我还是需要一一实现。”

“可以使用类似ModelMapper的东西将JPA实体转换为REST的DTO。”

“别忘了跟实习生强调下懒加载和JPA关联查询。”

“唉,其实都是雷同登录界面、千篇一律的实体到DTO的转换,有没有办法能让我摆脱所有这些乏味的东西,只需要专注于关键的业务逻辑实现呢”

本文适合使用Spring框架(包括SpringBoot)从头开始做过几个项目、并且正在考虑怎么提高自己工作效率的开发人员。在文章中,我将向您展示如何使用CUBA平台摆脱常见的耗时枯燥任务。

又一个新框架

开发人员在听到新框架时提出的一个问题往往是:“为什么我需要这个SpringBoot就能很好地帮助我从头开始实现所有内容”。没错,新平台意味着新的规则、新的限制,之前积累的其他框架的经验便失去了意义。就算是目前使用的框架不完美,但是对它足够了解,知道有哪些坑,和哪些变通的方法。

但是,从Spring切换到CUBA,并不需要重头学习规则,甚至没有什么变化,只要稍微前进一步就可以摆脱枯燥的开发任务,摆脱数百行DTO架子代码和转换工具的困扰,摆脱实现数据分页或数据过滤组件、创建SpringSecurity配置文件(JPA,Cache,)等等些基础功能的麻烦。

JPA全称为Java Persistence API(Java持久层API),它是Sun公司在JavaEE 5中提出的Java持久化规范。它为Java开发人员提供了一种对象/关联映射工具,来管理Java应用中的关系数据,JPA吸取了目前Java持久化技术的优点,旨在规范、简化Java对象的持久化工作。很多ORM框架都是实现了JPA的规范,如:Hibernate、EclipseLink。

Spring Data JPA旨在通过减少实际需要的工作量来显著改善数据访问层的实现。它在JPA的基础上做了一些封装,可以轻松实现基于JPA的存储库。 此模块处理对基于JPA的数据访问层的增强支持。 它使构建使用数据访问技术的Spring驱动应用程序变得更加容易。

需要注意的是JPA统一了Java应用程序访问ORM框架的规范

JPA为我们提供了以下规范:

以上的定义引用自网络技术文章,我还在不断理解与学习中,我们先来Demo一个例子:

5分钟入手Spring Boot

>

大约一年以前 我为了学习一些Hibernate专业知识 因此我参加了一个Hibernate项目 从那时起 我一直在使用Hibernate框架下的JPA(Java持久API)实现 使用的思想仍就是一样的 那个项目使用了一个数据库 这个数据库规模有些大 略显落后 并且还被许多的应用程序共用 为了尽快加入到项目中 我开始学习一些Hibernate知识 从书本上的例子开始学习 感觉很简单 学起来也很快 但是发现从零开始开发一个项目 并且控制它又是另外一回事了 试着在一个大型 复杂 被许多应用程序共用的数据库上使用Hibernate就又完全不同了 弄清楚了我可能遭遇到的技术难点 我开始想别的招了 要尽快从另外的方向开始 克服困难

在最终的学习和实践中 我发现我还是学到了许多重要的东西 虽然我们的项目还没有完全做完 但是我认为我们目前已经非常漂亮的应用了Hibernate/JPA的一些思想 现在我需要重新思考反省我所学到的东西 如下便是我学到的一些心得

)和数据库管理员成为朋友

目前存在一个趋势 就是一些Java开发者忽视数据库管理员的重要性 这便犯了一个很大的错误 对于要取得任何的ORM(对象关系映射)技术的成功 和数据库管理员保持一个良好的工作关系是至关重要的 有如下两个原因

单独数据库管理员虽然不能使Hibernate项目成功 但是他们通常可以让这些项目失败

数据库管理员对数据库本身具有很好的洞察力 很好的职业习惯 告诉你一些易犯的错误和 *** 作建议 我能记起这样的很多例子 一个数据库管理员的建议节约了我们很多的时间和提供给我们一个很好的解决方案

在大多数情况下 拥有好的数据库管理员 并且和他们保持良好的关系对你ORM(对象关系映射)工作至关重要

)从一开始使用(最好强制使用)好的命名标准

我们知道对命名标准的讨论将会有争议的 但是我们必须明确一件事情 我们的命名要让我们的数据模型有意义 这能让开发者使用起来简单 以免他们迷惑 所以 如何命名实体和属性是非常重要的 我有我喜欢命名标准 并且认为他们是最好的 但是在这里我不想把他们强加于你们 最重要的是你自己做出决定使用什么样的命名标准 并且让所有人使用它 实际上 不仅仅命名标准需要统一 其它的也需要(如 布尔型用 Y/N 或者 / 表示)

)不要试着映射所有的属性

我们总是设法使用工具 如Dali来映射所有的东西 然后形成一张表格(一些表格有上百列 !) 这最终会很麻烦 为什么?因为我们使用的是共用的 先前的数据库 有许多的字段是我们并不关心和从来不使用的 映射它们只会导致性能问题和造成混乱

)让数据库做自己擅长的工作

我们想有一个好的 清晰的数据模型 因此我们不惜任何代价写一些额外的查询语句来获取对象相关数据 要么使用存储过程 要么使用函数 这是做法是错误的 数据库优势在于存储 而不是保持Hibernate创建或读写的数据 举个例子 我们有一个对象 与之相关联的有一个状态 这个状态在整个应用程序中都要用到 因此 它毫无疑问是要执行的 但是 我们不想每次都要单独的写一个查询语句 这个问题在于 这个状态是从一些统计计算中派生出来的 并且这些统计计算需要用到一对多的关系 每次从加载的对象中读取数据的代价是非常高的 后来跟我们其中的一位数据库管理员交流了一下 发现一个我们可以使用的sql函数能够很快的获得该状态 我们使用@Formula来映射成一个状态属性 就能得到我们所需要的所有东西 这仍就是域模型的一部分 但是执行起来非常好 有时像这样的一个折衷的办法能够起到很大的效果

)分解数据库

在一开始 我就想在Hibernate中模型化整个数据库 结果发现这是不切实际的 原因如下 a)这是一项巨大的工程 并且要花费几周的时间 而用户根本看不到你做了什么实际的工作

b)我不可能在第一次就把它弄好 后继的开发者无论如何都会修改它们的

现在有一个趋势 就是希望在开始之前 将所有的事情都进行映射 但是 当时你开始这么做后 你不需在这上面花很多的时间 我后来发现一个好的办法 就是将数据库分解 工作的时候一块一块的进行 发现这很有帮助

)密切注意触发器

密切注意数据库触发器有如下两个原因

a)在后台触发器很隐蔽的执行了一些功能 让你很是疑惑 不知道发生了什么

b)当你在Hibernate端需要复制一些东西的时候 触发器会做一些手脚 之前我们好几次没有认识到这个教训 导致我们丢失了很多数据 这些都是由触发器引起的 这几乎让我们很是郁闷

)避免使用工具来自动生成你的模型

没错 这些工具的使用可以节约时间(虽然我们发现了Dali有一个很严重的bug 但是我们还是使用它) 但是最后你不得不重新做很多的事情 其实手动也花费不了你很多的时间 当你亲自做的时候 这可以让你有机会熟悉那些数据

) 尽量多的使用命名查询语句(NamedQueries)

虽然很容易写查询语句 但是在许多的情况下 使用NamedQueries会更好 这会有助于你完成两件事情

a)它能更加重用 因为被命名的查询语句通常在代码的重要地方

b)你的查询语句在开始的时候就是正确的 那么在查询语句中的错误更加容易发现

要习惯这样做需要花一些时间 但是这么做是值得的

)预期管理

对于任何一种框架 技术 甚至观念来说 这是非常重要的 要铭记在心 由于某些原因 人们倾向于专注某一个特征 这些特征实际上或许不存在 或许被夸大 有时它很小 很容易理解(举个例子 理解一些实际的工作 需要在Hibernate中映射) 有时我也不知道他们是如何管理实现一些概念(如Hibernate是如何管理计划修正的) 无论如何 找到预期目标是什么 然后管理它们是非常重要的 如果你的团队认为Hibernate会使得数据库管理员没有用处 把他们解雇 那么你将会有一个潜在的问题存在

)使用富域模型(rich domain modeling)

lishixinzhi/Article/program/Java/ky/201311/28274

以上就是关于EJB之JPA(事务回滚)全部的内容,包括:EJB之JPA(事务回滚)、hibernate,jpa数据库连接问题。我没法确定要连接什么数据库,但是我要用原生sql来update数据、北京java课程分享应用开发会使用哪些框架工具等相关内容解答,如果想了解更多相关内容,可以关注我们,你们的支持是我们更新的动力!

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/sjk/9516012.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2023-04-29
下一篇 2023-04-29

发表评论

登录后才能评论

评论列表(0条)

保存