JPA概述及常用注解详解SpringDataJpa使用指南

丰富的线上&线下活动,深入探索云世界

做任务,得社区积分和周边

最真实的开发者用云体验

让每位学生受益于普惠算力

让创作激发创新

资深技术专家手把手带教

遇见技术追梦人

技术交流,直击现场

海量开发者使用工具、手册,免费下载

极速、全面、稳定、安全的开源镜像

开发手册、白皮书、案例集等实战精华

为开发者定制的Chrome浏览器插件

JPA(JavaPersistenceAPI)是Java标准中的一套ORM规范(提供了一些编程的API接口,具体实现由ORM厂商实现,如Hiernate、TopLink、Eclipselink等都是JPA的具体实现),借助JPA技术可以通过注解或者XML描述【对象-关系表】之间的映射关系,并将实体对象持久化到数据库中(即ObjectModel与DataModel间的映射)。

JPA是Java持久层API,由Sun公司开发,希望规范、简化Java对象的持久化工作,整合ORM技术,整合第三方ORM框架,建立一种标准的方式,目前也是在按照这个方向发展,但是还没能完全实现。在ORM框架中,Hibernate框架做了较好的JPA实现,已获得Sun的兼容认证。

JPA的优势:

1.开发者面向JPA规范的接口,但底层的JPA实现可以任意切换:觉得Hibernate好的,可以选择HibernateJPA实现;觉得TopLink好的,可以选择TopLinkJPA实现。

2.开发者可以避免为使用Hibernate学习一套ORM框架,为使用TopLink又要再学习一套ORM框架。

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

Hibernate介绍

Hibernate对数据库结构提供了较为完整的封装,Hibernate的O/RMapping实现了POJO和数据库表之间的映射,以及SQL的自动生成和执行。往往只需定义好了POJO到数据库表的映射关系,即可通过Hibernate提供的方法完成持久层操作。甚至不需要对SQL的熟练掌握,Hibernate/OJB会根据制定的存储逻辑,自动生成对应的SQL并调用JDBC接口加以执行。

Hibernate框架(3.2及以上版本)对JPA接口规范做了较好的实现,主要是通过以下三个组件来实现的:

hibernate对JPA的支持,不是另提供了一套专用于JPA的注解。一些重要的注解如@Column,@OneToMany等,hibernate并没有提供,这说明JPA的注解已经是hibernate的核心,hibernate只提供了一些补充,而不是两套注解。JPA和hibernate都提供了的注解(例如@Entity),若JPA的注解够用,就直接用,若JPA的注解不够用,直接使用hibernate的即可。

SpringDataJPA介绍

SpringDataJPA是在实现了JPA规范的基础上封装的一套JPA应用框架(CriteriaAPI还是有些复杂)。虽然ORM框架都实现了JPA规范,但是在不同的ORM框架之间切换仍然需要编写不同的代码,而使用SpringDataJPA能够方便的在不同的ORM框架之间进行切换而不需要更改代码。SpringDataJPA旨在通过统一ORM框架的访问持久层的操作,来提高开发人的效率。

SpringDataJPA是一个JPA数据访问抽象。也就是说SpringDataJPA不是一个实现或JPA提供的程序,它只是一个抽象层,主要用于减少为各种持久层存储实现数据访问层所需的样板代码量。但是它还是需要JPA提供实现程序,其实SpringDataJPA底层就是使用的Hibernate实现。

SpringDataJPA其实并不依赖于Spring框架。

SpringDataJPA通过Repository来支持上述功能,默认提供的几种Repository已经满足了绝大多数需求:

JPA与springDataJpa、Hibernate之间的关系

Querydsl-JPA介绍

Springdata-JPA是对JPA使用的封装,Querydsl-JPA也是基于各种ORM之上的一个通用查询框架,使用它的API类库可以写出“Java代码的sql”,不用去手动接触sql语句,表达含义却如sql般准确。更重要的一点,它能够构建类型安全的查询,这比起JPA使用原生查询时有很大的不同,可以不必再对恶心的“Object[]”进行操作了。SpringDataJPA+Querydsl-JPA联合使用方案是使用JPA操作数据库的最佳方案,它们之间有着完美的相互支持,以达到更高效的编码。

指定对象与数据库字段映射时注解的位置:如@Id、@Column等注解指定Entity的字段与数据库字段对应关系时,注解的位置可以在Field(属性)或Property(属性的get方法上),两者统一用其中一种,不能两者均有。推荐用前者。

@Entity(必需)

标注在实体类上。

映射实体类。指出该Java类为实体类,将映射到指定的关系数据库表。

应用了此注解后,将会自动将类名映射作为数据库表名、将类内的字段名映射为数据库表的列名。映射策略默认是按驼峰命名法拆分将类名或字段名拆分成多部分,然后以下划线连接,如StudentEntity->student_entity、studentName->student_name。若不按默认映射,则可通过@Table、@Column指定,见下面。

@Table(可选)

映射数据库表名。当实体类与其映射的数据库表名不同名时需要使用@Table标注说明,该标注与@Entity标注并列使用

@DynamicInsert(可选)

设置为true,表示insert对象的时候,生成动态的insert语句,如果这个字段的值是null就不会加入到insert语句中,默认false。

@DynamicUpdate(可选)

设置为true,表示执行update对象时,在生成动态的update语句前,会先查询该表在数据库中的字段值,并对比更新使用的对象中的字段值与数据库中的字段值是否相同,若相同(即该值没有修改),则该字段就不会被加入到update语句中。

默认false,表示无论更新使用实体类中的字段值与数据库中的字段值是否一致,都加入到update语句中,即都使用对象中所有字段的值覆盖数据库中的字段值。

比如只想更新某个属性,但是却把整个属性都更改了,这并不是我们希望的结果,我们希望的结果是:我更改了哪写字段,只要更新我修改的字段就够了。

注意:

@DynamicUpdate的动态更新的含义是,比较更新要使用的实体类中的所有字段值与从数据库中查询出来的所有字段值,判断其是否有修改,不同则加入到update语句中更新字段值。看这个例子,数据库中id=1的记录所有字段都是非空的,但是实体类中只有name有值,也就是所有字段都变了,只是其他字段被更新为了新的空值。

所以jpa更新数据库字段值,无论是否有@DynamicUpdate注解,均需要手动先select对象,然后通过set更新对象的属性值,然后再save对象,实现更新操作

@Id(必需)

标注在实体类成员变量或getter方法之上。

若同时指定了下面的@GeneratedValue则存储时会自动生成主键值,否则在存入前用户需要手动为实体赋一个主键值。

主键值类型可以是:

指定联合主键,有@IdClass、@EmbeddedId两种方法。

@GeneratedValue

@GeneratedValue用于标注主键的生成策略,通过strategy属性指定。

默认情况下,JPA自动选择一个最适合底层数据库的主键生成策略:SqlServer对应identity,MySQL对应autoincrement

@Column(可选)

映射表格列。当实体的属性与其映射的数据库表的列不同名时需要使用@Column标注说明。

类的字段名在数据库中对应的字段名可以通过此注解的name属性指定,不指定则默认为将属性名按驼峰命名法拆分并以下划线连接,如createTime对应create_time。

注意:即使name的值中包含大写字母,对应到db后也会转成小写,如@Column(name="create_Time")在数据库中字段名仍为create_time。可通过SpringBoot配置参数【spring.jpa.hibernate.naming.physical-strategy】配置对应策略,如指定name值是什么,数据库中就对应什么名字的列名。默认值为:【org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy】

@Column注解一共有10个属性,这10个属性均为可选属性,各属性含义分别如下:

@Basic(可选)

表示一个简单的属性到数据表的字段的映射,对于没有任何标注的属性或getter方法,默认标注为@Basic

@Transient:忽略属性

定义暂态属性。表示该属性并非一个到数据库表的字段的映射,ORM框架将忽略该属性。

如果一个属性并非数据库表的字段映射,就必须将其标识为@Transient,否则ORM框架默认为其注解@Basic,例如工具方法不需要映射。

标注在实体类成员变量或getter方法之上。可选。

在JavaAPI中没有定义Date类型的精度,而在数据库中表示Date类型的数据类型有Date(年月日),Time(时分秒),TimeStamp(年月日时分秒)三种精度,进行属性映射的时候可以使用@Temporal注解调整精度。

目前此注解只能用于修饰JavaAPI中的【java.util.Date】、【java.util.Calendar】类型的变量,TemporalType取DATE、TIME、TIMESTAMP时在MySQL中分别对应的DATE、TIME、DATETIME类型。

示例:

@Temporal(TemporalType.TIMESTAMP)@CreationTimestamp//org.hibernate.annotations.CreationTimestamp,用于在JPA执行insert操作时自动更新该字段值@Column(name="create_time",updatable=false)//为防止手动set,可设false以免该字段被更新privateDatecreateTime;@Temporal(TemporalType.TIMESTAMP)@UpdateTimestamp//org.hibernate.annotations.UpdateTimestamp,用于在JPA执行update操作时自动更新该字段值privateDateupdateTime;

1、Hibernate的注解:

用法:

标注为@MappedSuperclass的类将不是一个完整的实体类,将不会映射到数据库表,但是其属性都将映射到其子类的数据库字段中。

标注为@MappedSuperclass的类不能再标注@Entity或@Table注解,也无需实现序列化接口。

允许多级继承

注解的类继承另一个实体类或标注@MappedSuperclass类,可以使用@AttributeOverride或@AttributeOverrides注解重定义其父类属性映射到数据库表中字段。

指定联合主键类。如:@IdClass(StudentExperimentEntityPK.class)

主键类StudentExperimentEntityPK需要满足:

/***实体类*/@Data@Entity@Table(name="customer_course")@IdClass(CustomerCourseEntityPK.class)//指定联合主键类publicclassCustomerCourseEntity{@Id@Column(name="customer_id",length=ColumnLengthConstrain.LEN_ID_MAX)privateStringcustomerId;@Id@Column(name="course_id",length=ColumnLengthConstrain.LEN_ID_MAX)privateStringcourseId;@Column(name="max_number")privateIntegermaxNumber;@ManyToOne@JoinColumn(name="course_id",referencedColumnName="id",nullable=false,insertable=false,updatable=false)privateCourseEntitycourseByCourseId;}/***联合主键类*/@DatapublicclassCustomerCourseEntityPKimplementsSerializable{privatestaticfinallongserialVersionUID=1L;privateStringcustomerId;privateStringcourseId;}

标注在实体类成员变量或getter方法上。

功能与@IdClass一样用于指定联合主键。不同的是其标注在实体内的主键类变量上,且主键类应该标注@Embeddable注解。

此外在主键类内指定的字段在实体类内可以不再指定,若再指定则需为@Column加上insertable=false,updatable=false属性

@Data@Entity@Table(name="customer_course")@IdClass(CustomerCourseEntityPK.class)//指定联合主键类publicclassCustomerCourseEntity{@EmbeddedIdprivateCustomerCourseEntityPKid;@Column(name="max_number")privateIntegermaxNumber;}/***联合主键类*/@Data@EmbeddablepublicclassCustomerCourseEntityPKimplementsSerializable{privatestaticfinallongserialVersionUID=1L;@Column(name="customer_id",length=ColumnLengthConstrain.LEN_ID_MAX)privateStringcustomerId;@Column(name="course_id",length=ColumnLengthConstrain.LEN_ID_MAX)privateStringcourseId;}

用于表结构复用。指定被该注解修饰的类被子类继承后子类和父类的表结构的关系。

通过strategy属性指定关系,有三种策略:

@Inheritance与@MappedSuperclass的区别:

@Inheritance、@MappedSuperClass可用于定义Inheritance关系。这些方式的一个缺点是子类中无法覆盖从父类继承的字段的定义(如父类中name是notnull,但子类中允许为null)。

除了@Inheritance、@MappedSuperClass外,还有一种Inheritance方法(此法可解决上述不足):先定义一个JavaPOJO(干净的POJO,没有任何对该类使用任何的ORM注解),然后不同子类继承该父类,并分别在不同子类中进行ORM定义即可。此法下不同子类拥有父类的公共字段且该字段在不同子类中对应的数据库列定义可不同。

当一个实体类要在多个不同的实体类中进行使用,而其不需要生成数据库表

@Embeddable:标注在类上,表示此类是可以被其他类嵌套

@Embedded:标注在属性上,表示嵌套被@Embeddable注解的同类型类

使用此注解映射枚举字段,以String类型存入数据库

注入数据库的类型有两种:EnumType.ORDINAL(Interger)、EnumType.STRING(String)

TableGenerator定义一个主键值生成器,在@GeneratedValue的属性strategy=GenerationType.TABLE时,generator属性中可以使用生成器的名字。生成器可以在类、方法或者属性上定义。

生成器是为多个实体类提供连续的ID值的表,每一行为一个类提供ID值,ID值通常是整数。

属性说明:

@EntitypublicclassEmployee{@Id@Column(name="id")@TableGenerator(name="hf_opert_id_gen",//此处的名字要和下面generator属性值一致table="mcs_hibernate_seq",//主键保存到数据库的表名pkColumnName="sequence_name",//表里用来保存主键名字的字段valueColumnName="sequence_next_hi_value",//表里用来保存主键值的字段pkColumnValue="user_id",//表里名字字段对应的值allocationSize=1)//自动增长,设置为1@GeneratedValue(strategy=GenerationType.TABLE,generator="hf_opert_id_gen")privateIntegerid;}

@JoinColumn:指定外键

如果在实体类的某个属性上定义了联表关系(OneToOne或OneTOMany等),则使用@JoinColumn注解来定义关系的属性。JoinColumn的大部分属性和Column类似。

@Data@EntitypublicclassPerson{...//Person和Address是一对一关系。Address表中名为id_address的列作为外键指向Person表中名为address_id的列@OneToOne@JoinColumn(name="address_id",referencedColumnName="id_address",unique=true)privateAddressaddress;}@Data@EntitypublicclassAddress{@Id@column(name="id_address")privateIntegeridAddress;}

@JoinColumns

如果在实体类的某个属性上定义了联表关系(OneToOne或OneTOMany等),并且关系存在多个JoinColumn,则使用@JoinColumns注解定义多个JoinColumn的属性。

@Data@EntitypublicclassCustom{//Custom和Order是一对一关系。Order表中一个名为CUST_ID的列作为外键指向Custom对应表中名为ID_CUST的列,另一名为CUST_NAME的列作为外键指向Custom对应表中名为NAME_CUST的列@OneToOne@JoinColumns({@JoinColumn(name="CUST_ID",referencedColumnName="ID_CUST"),@JoinColumn(name="CUST_NAME",referencedColumnName="NAME_CUST")})privateOrderorder;}

@OneToOne

描述一个一对一的关联

@Data@EntitypublicclassPerson{...//Person和Address是一对一关系。Address表中名为id_address的列作为外键指向Person表中名为address_id的列@OneToOne@JoinColumn(name="address_id",referencedColumnName="id_address",unique=true)privateAddressaddress;}

@OneToMany

描述一个一对多的关联,该属性应该为集体类型,在数据库中并没有实际字段。

例如:实体User和Order是OneToMany的关系,则实体User被删除时,其关联的实体Order也应该被全部删除

@ManyToOne

表示一个多对一的映射,该注解标注的属性通常是数据库表的外键

@ManyToMany

描述一个多对多的关联。多对多关联上是两个一对多关联,但是在ManyToMany描述中,中间表是由ORM框架自动处理

两个实体间相互关联的属性必须标记为@ManyToMany,并相互指定targetEntity属性,需要注意的是,有且只有一个实体的@ManyToMany注解需要指定mappedBy属性,指向targetEntity的集合属性名称,利用ORM工具自动生成的表除了User和Book表外,还自动生成了一个User_Book表,用于实现多对多关联

@JsonFormat

spring.jackson.date-format=yyyy-MM-ddHH:mm:ssspring.jackson.time-zone=GMT+8

@DateTimeFormat

通过annotation来映射hibernate实体,基于annotation的hibernate主键标识@Id,由@GeneratedValue设定其生成规则。

用于标注主键的生成策略,通过strategy属性指定。默认情况下,JPA自动选择一个最适合底层数据库的主键生成策略:SqlServer对应identity,MySQL对应autoincrement。

源码定义:

@Target({METHOD,FIELD})@Retention(RUNTIME)public@interfaceGeneratedValue{GenerationTypestrategy()defaultAUTO;Stringgenerator()default"";}其中GenerationType:

publicenumGenerationType{TABLE,SEQUENCE,IDENTITY,AUTO}JPA提供的四种标准用法为:

1、AUTO用法

//使用示例@Id@GeneratedValue(strategy=GenerationType.AUTO)在指定主键时,如果不指定主键生成策略,默认为AUTO。

//使用示例@Id此时主键生成策略,为默认值AUTO。

以下指定主键生成策略为AUTO,效果同上:

//使用示例@Id@GeneratedValue(strategy=GenerationType.AUTO)

2、IDENTITY用法

//使用示例@Id@GeneratedValue(strategy=GenerationType.IDENTITY)

3、TABLE用法

//使用示例。@Id@GeneratedValue(strategy=GenerationType.TABLE,generator="pk_gen")@TableGenerator(name="pk_gen",table="tb_generator",pkColumnName="gen_name",valueColumnName="gen_value",pkColumnValue="PAYABLEMOENY_PK",allocationSize=1)使用此策略需要数据库存在相应的表。此处应用表tb_generator,定义为:

CREATETABLEtb_generator(idNUMBERNOTNULL,gen_nameVARCHAR2(255)NOTNULL,gen_valueNUMBERNOTNULL,PRIMARYKEY(id))插入纪录,供生成主键使用

INSERTINTOtb_generator(id,gen_name,gen_value)VALUES(1,PAYABLEMOENY_PK',1);在主键生成后,这条纪录的value值,按allocationSize递增。

//@TableGenerator的源码定义:@Target({TYPE,METHOD,FIELD})@Retention(RUNTIME)public@interfaceTableGenerator{Stringname();Stringtable()default"";Stringcatalog()default"";Stringschema()default"";StringpkColumnName()default"";StringvalueColumnName()default"";StringpkColumnValue()default"";intinitialValue()default0;intallocationSize()default50;UniqueConstraint[]uniqueConstraints()default{};}以上属性说明如下:

4、SEQUENCE用法

//使用示例@Id@GeneratedValue(strategy=GenerationType.SEQUENCE,generator="aaa")@SequenceGenerator(name="aaa",sequenceName="seq_payment")@SequenceGenerator源码定义:

@Target({TYPE,METHOD,FIELD})@Retention(RUNTIME)public@interfaceSequenceGenerator{Stringname();StringsequenceName()default"";intinitialValue()default0;intallocationSize()default50;}以上属性说明如下:

hibernate提供多种主键生成策略,有点是类似于JPA,基于Annotation的方式通过@GenericGenerator实现

以下是hibernate特有的:

对于这些hibernate主键生成策略和各自的具体生成器之间的关系,在org.hibernate.id.IdentifierGeneratorFactory中指定了:

static{GENERATORS.put("uuid",UUIDHexGenerator.class);GENERATORS.put("hilo",TableHiLoGenerator.class);GENERATORS.put("assigned",Assigned.class);GENERATORS.put("identity",IdentityGenerator.class);GENERATORS.put("select",SelectGenerator.class);GENERATORS.put("sequence",SequenceGenerator.class);GENERATORS.put("seqhilo",SequenceHiLoGenerator.class);GENERATORS.put("increment",IncrementGenerator.class);GENERATORS.put("foreign",ForeignGenerator.class);GENERATORS.put("guid",GUIDGenerator.class);GENERATORS.put("uuid.hex",UUIDHexGenerator.class);//uuid.hexisdeprecatedGENERATORS.put("sequence-identity",SequenceIdentityGenerator.class);}//上面十二种策略,加上native,hibernate一共默认支持十三种生成策略。

使用示例:

hibernate每种主键生成策略提供接口org.hibernate.id.IdentifierGenerator的实现类,如果要实现自定义的主键生成策略也必须实现此接口。

IdentifierGenerator提供一generate方法,generate方法返回产生的主键。

//源码展示publicinterfaceIdentifierGenerator{/***Theconfigurationparameterholdingtheentityname*/publicstaticfinalStringENTITY_NAME="entity_name";/***Generateanewidentifier.*@paramsession*@paramobjecttheentityortoplevelcollectionforwhichtheidisbeinggenerated**@returnanewidentifier*@throwsHibernateException*/publicSerializablegenerate(SessionImplementorsession,Objectobject)throwsHibernateException;}

由@GenericGenerator实现

@Target({PACKAGE,TYPE,METHOD,FIELD})@Retention(RUNTIME)public@interfaceGenericGenerator{/***uniquegeneratorname*/Stringname();/***GeneratorstrategyeitherapredefinedHibernate*strategyorafullyqualifiedclassname.*/Stringstrategy();/***Optionalgeneratorparameters*/Parameter[]parameters()default{};}以上属性说明如下:

通过@GenericGenerator自定义主键生成策略

@Query:自定义JPQL或原生Sql查询,摆脱命名查询的约束

@Query("selectufromUseruwhereu.firstname=:firstname")//JPQLUserfindByLastnameOrFirstname(@Param("lastname")Stringlastname);@Query(value="SELECT*FROMUSERSWHEREX=1",nativeQuery=true)//原生sqlUserfindByEmailAddress(StringX);关于@Query中参数的占位符:

@Modifying:DELETE和UPDATE操作必须加上@modifying注解,以通知SpringData这是一个DELETE或UPDATE操作。

@Transactional:UPDATE或者DELETE操作需要使用事务

@Async:异步操作

@NoRepositoryBean:避免Spring容器为此接口创建实例。可用于定义公共Repository

基本使用步骤

依赖

org.springframework.bootspring-boot-starter-data-jpamysqlmysql-connector-javaorg.postgresqlpostgresql

Entity类

@Data@Builder@AllArgsConstructor@NoArgsConstructor@Table(name="CUSTOMERS")@Entity@DynamicInsert@DynamicUpdatepublicclassCustomer{@Id@GeneratedValue(strategy=GenerationType.AUTO)@Column(name="id",insertable=false,updatable=false,length=32)privateIntegerid;@Column(name="name",nullable=false,length=10)privateStringname;@Column(name="age")privateIntegerage;@Temporal(TemporalType.TIMESTAMP)@CreationTimestamp@Column(name="create_date",columnDefinition="timestamp(6)")privateDatecreateDate;@Temporal(TemporalType.TIMESTAMP)@UpdateTimestamp@Column(name="update_date",columnDefinition="timestamp(6)")privateDateupdateTime;}Respository接口

//Customer为该respository对应的实体类,Long为实体类的主键的类型publicinterfaceCustomerRespositoryextendsJpaRespository{}

注解扫描

在SpringBoot中:

JPA和JDBC常用配置

在利用SpringBoot框架进行开发时,大部分服务避不开用数据库进行数据存储和使用。SpringBoot里面一般有两种方式进行数据表的创建和数据存储。

配置模板:

spring:datasource:#driver-class-name:com.mysql.jdbc.Driverdriver-class-name:com.mysql.cj.jdbc.Driverurl:jdbc:mysql://localhost:3306/testuseUnicode=true&characterEncoding=utf8&serverTimezone=UTCusername:rootpassword:roottype:com.alibaba.druid.pool.DruidDataSource#数据源配置schema:classpath:db/schema.sql#建表语句脚本的存放路径data:classpath:db/data.sql#数据库初始化数据的存放路径sql-script-encoding:UTF-8#设置脚本的编码jpa:database:mysql#配置数据库方言。使用JPA访问数据库时,必需配置。hibernate:ddl-auto:none#每次程序启动时的数据库初始化策略database-platform:org.hibernate.dialect.MySQL5InnoDBDialect#配置数据库引擎,不配置则默认为myisam引擎show-sql:true#日志打印执行的SQLproperties:hibernate:#show_sql:true#日志打印执行的SQL。与spring.jpa.show-sql配置效果相同,两者使用其一即可。format_sql:true#格式化sql语句配置说明:

spring.datasource.xxx

spring.datasource.driver-class-name:配置driver的类名,默认是从JDBCURL中自动探测

spring.datasource.url:配置数据库JDBC连接串

spring.datasource.username:配置数据库连接用户名

spring.datasource.password:配置数据库连接用户名对应的密码

spring.datasource.type:连接池配置

spring.datasource.schema:使用脚本创建表的语句的存放路径,classpath/db表示在工程的resource层级下的db目录中存放

spring.datasource.data:使用脚本初始化数据库数据的语句的存放路径

spring.datasource.sql-script-encoding:设置脚本的编码,默认常用设置为UTF-8

使用上述方式建表时,spring.jpa.hibernet.ddl-auto设置成none,否则有啥问题,我也没尝试过。这样配置可以避免两种方式一起使用

spring.jpa.xxx

spring.jpa.hibernet.ddl-auto值说明:

spring.jpa.database:配置数据库方言,使用JPA访问数据库时,必需配置。

spring.jpa.database-platform:配置数据库引擎。SpringBoot2.0后使用JPA、Hibernate来操作MySQL,Hibernate默认使用MyISM存储引擎而非InnoDB,前者不支持外键故会忽略外键定义。

使用JPA访问数据库的注意事项:

通过方法名来指定查询逻辑,而不需要自己实现查询的SQL逻辑,示例:ListgetByName(Stringname)

方法名解析原理:

对方法名中除了保留字(findBy、top、within等)外的部分以and为分隔符提取出条件单词,然后解析条件获取各个单词并看是否和Entity中的属性对应(不区分大小写进行比较)。

注意:get/find与by之间的会被忽略,所以getNameById与getById是等价的,会根据id查出整个Entity而不会只查name字段(指定部分字段的查询见后面条目)。

查询条件解析原理:

假设School和Student是一对多关系,Student中有个Schoolschool字段、School有个StringaddressCode字段,以如下查询为例:

//查询student表StudetngetByNameAndSchoolAddressCode(StringstudentName,StringaddressCode)//JPA会自动生成条件studentName和关联条件student.school.addressCode进行查询//查询student表,推荐写法StudetngetByNameAndSchool_AddressCode(StringstudentName,StringaddressCode)

查询字段解析原理:

默认会查出Entity的所有字段且返回类型为该Entity类型,有两种情况可查询部分字段(除此外都会查出所有字段):

JPA集合类型查询参数

ListgetByIdInAndSchoolId(CollectionstudentIdList,StringschoolId);关键在于In关键字。参数用Collection类型,当然也可以用List、Set等,但用Collection更通用,因为此时实际调用可以传List、Set等实参。

查询关键字

在查询时,通常需要同时根据多个属性进行查询,且查询的条件也格式各样(大于某个值、在某个范围等等),SpringDataJPA为此提供了一些表达条件查询的关键字,官方文档如下:

Example查询翻译过来叫”按例查询(QBE)”。是一种用户界面友好的查询技术。它允许动态创建查询,并且不需要编写包含字段名称的查询。而且按示例查询不需要使用特定的数据库的查询语言来编写查询语句。

优势:

劣势:

Example(动态实例)查询的原理

从生成的SQL语句可以看到,它的判断条件是根据实体的属性来生成查询语句的。如果实体的属性是null,它就会忽略它;如果不是,就会取其值作为匹配条件。

注意:如果一个字段是不是包装类型,而是基本类型,它也会参与where条件中,其值是默认值。所以在定义实体时,基本数据类型的字段应尽量使用包装类型。

@Testpublicvoidtest01(){Useruser=User.builder().name("Bob").build();Exampleexample=Example.of(user);Listlist=userRepository.findAll(example);list.foreach(System.out::println)OptionaluserOptional=userRepository.findOne(Example.of(user));userOptional.ifPresent(x->System.out.println(x.getName()).isEqualTo("Bob"));}

Example(动态实例)查询的概念定义介绍:

//示例:Example对象,由customer和matcher共同创建Exampleex=Example.of(customer,matcher);实例查询:就是通过一个例子来查询。要查询的是Customer对象,查询条件也是一个Customer对象,通过一个现有的客户对象作为例子,查询和这个例子相匹配的对象

自定匹配器规则

ExampleMatcher,不传时会使用默认的匹配器。

StringMatcher参数

说明:

总结

ExampleMatcher的使用:

实体对象中,基本数据类型无论是否传值,都会参与查询(因为有默认值),故应避免使用基本数据类型,采用包装器类型(默认值是null)。如果已经采用了基本类型,而这个属性查询时若不需要进行过滤,则需把它添加到忽略列表(ignoredPaths)中。

当条件值为null时,默认是忽略此过滤条件,一般业务也是采用这种方式就可满足。当需要查询数据库表中属性为null的记录时,可将值设为include,这时,对于不需要参与查询的属性,都必须添加到忽略列表(ignoredPaths)中,否则会出现查不到数据的情况(见上面实例test04)。

若属性值为null,默认忽略该过滤条件;若属性值为基本数据类型,默认参与查询,若需忽略,则需添加至则需把它添加到忽略列表(ignoredPaths)中。

默认创建匹配器时,字符串采用的是精确匹配、不忽略大小写,可以通过操作方法改变这种默认匹配,以满足大多数查询条件的需要,如将“字符串匹配方式”改为CONTAINING(包含,模糊匹配),这是比较常用的情况。对于个别属性需要特定的查询方式,可以通过配置“属性特定查询方式”来满足要求(见上面实例test02)。

忽略大小的生效与否,是依赖于数据库的。例如MySql数据库中,默认创建表结构时,字段是已经忽略大小写的,所以这个配置与否,都是忽略的。如果业务需要严格区分大小写,可以改变数据库表结构属性来实现,具体可百度(见上面实例test03)。

JPQL是专门为Java应用程序访问和导航实体实例设计的。JavaPresistenceQueryLanguage(JPQL),java持久性查询语言。它是JPA规范的重要组成部分,其实它就是一种查询语言,语法类似于SQL语法,但是有着本质的区别。

JPQL与SQL的区别

JPQL是面向对象的查询语言,因此它可以完全理解继承、多态和关联等特征。而且JPQL内置了大量函数,极大地方便了JPQL查询的功能。当然JPQL底层依然是基于SQL的,但JPQL到SQL的转换无须开发者关心,JPQL解析器会负责完成这种转换,并负责执行这种转换的SQL语句来更新数据库。

SQL是面向关系数据库的查询语言,因此SQL操作的对象是数据表、数据列;而JQPL操作的对象是实体对象,对象属性。

代码对比

//原生的SQL语句。对t_usertable表执行查询,查询name、age、user_id三个数据列selectname,age,user_idfromt_user//面向对象的JPQL语句。对User实体执行查询,查询的是User实体的name、age、userId属性selectname,age,userIdfromUser比较项SQLJPQL面向处理关系数据处理JPA实体关联实体的方式内连接、外连接、左连接、右连接内连接和左外连接支持的操作增(Insert)、删(Delete)改(Update)、查(Select)Delete(remove)Update(merge)、Select(find)

JPQL基本语法

select实体别名.属性名,实体别名.属性名……from实体名[as]实体别名where实体别名.实体属性op比较值使用@Query注解创建查询,将该注解标注在Repository的方法上,然后提供一个需要的JPQL语句即可,如:

@Query("selectpfromPersonpwherenamelike%1%")PersonfindByName(Stringname);JPQL查询时,可以使用SpEL表达式:#{#entityName}(取数据库实体名称)

好处是当修改类名后,不需要再单独去修改JPQL中的实体类名称

@Query("selectpfrom#{#entityName}pwherenamelike%1%")PersonfindByName(Stringname;SpEL表达式了解:

SpEL(SpringExpressionLanguage),即Spring表达式语言。它是一种类似JSP的EL表达式、但又比后者更为强大有用的表达式语言。SpEL表达式可以在spring容器内实时查询和操作数据,尤其是操作List列表型、Array数组型数据。所以使用SpEL可以有效缩减代码量,优化代码结构。

@Query注解查询时候,条件查询如何使用占位符:

(1)?+数字

若使用这种方式,则参数列表中参数的入参顺序必须与@Query注解当中标注的顺序相同

@Query("SELECTsfromStudentswheres.email=1ands.age=2")StudentfindStudentByEmailAndAge(Stringemail,Integerage);(2):+参数名称

这种方式可以自定义参数的名称。需要在参数列表当中用@Param注解标注参数名称。不用考虑顺序,是根据参数名称进行绑定

@Query("SELECTsfromStudentswheres.email=:emailands.age=:age")StudentfindStudentByEmailAndAge2(@Param("age")Integerage,@Param("email")Stringemail);

nativeQuery(原生SQL查询)

nativeQuery返回Entity

使用nativeQuery时SQL语句查询的字段名若没有取别名,则默认是数据库中的字段名(例如school_id),而API返回值通常是schoolId,可以在SQL里通过school_idasschoolId取别名返回。

然而若查询很多个字段值则得一个个通过as取别名,很麻烦,可以直接将返回值指定为数据库表对应的Entity,不过此法要求查询的是所有字段名,如:

排序查询

静态方式:直接在方法体现(如getByNameOrderByIdDesc),也可以在JPQL的@Query的逻辑中使用orderby进行排序

动态方式:可以在Repository的方法的最后加一个Sort或者Pageable类型的参数,便可动态生成排序或分页语句(编译后会自动在语句后加orderby或limit语句)

ListfindByAndSort(Stringname,Sortsort);//调用UserRepository.findByAndSort("bolton",Sort.by(Direction.Desc,"age"));进阶(了解)——可以通过JpaSort.unsafe实现待function(函数计算)的sort(排序):

publicinterfaceUserRepositoryextendsJpaRepository{@Query("selectufromUseruwhereu.lastnamelike1%")ListfindByAndSort(Stringlastname,Sortsort);@Query("selectu.id,LENGTH(u.firstname)asfn_lenfromUseruwhereu.lastnamelike1%")ListfindByAsArrayAndSort(Stringlastname,Sortsort);}/***调用*/userRepository.findByAndSort("lannister",newSort("firstname"));//userRepository.findByAndSort("stark",newSort("LENGTH(firstname)"));//报错:invalid无效userRepository.findByAndSort("targaryen",JpaSort.unsafe("LENGTH(firstname)"));

分页查询

动态方式:在Repository的方法的最后加一个Pageable类型的参数,便可动态生成分页语句(编译后会自动在语句后加limit语句)

查询一个表的部分字段,称为投影(Projection)

@Query(value="selectp.namefromPserSonpwherep.id=1")StringfindNameById(Stringid);@Query(value="selectnamefromPserSonwhereage=1")SetfindNameByAge(Stringid);对于返回多个字段的查询:

1.使用自定义接口来映射结果集

直接通过方法名命名指定返回对象为包含部分字段getter方法的自定义接口(interface),只需要定义属性的getter方法,jdk动态代理封装数据。

注意:如果不用@Query则需要确保接口中getter方法名中的字段与Entity中的一致,而如果使用@Query则不需要,因为可以通过as取别名

/***Repository自定义查询方法*/ListgetLanguagesTypeByCourseIdIn(CollectioncourseIdCollection);/***自定义接口来映射结果*/publicinterfaceIdAndLanguageType{StringgetIdx();StringgetLanguageType();defaultStringgetAll(){returngetIdx()+","+getLanguageType();}}publicinterfacePersonSummary{StringgetFirstname();StringgetLastname();ListgetAddress();publicinterfaceAddressSummary{StringgetCity();}}/***投影接口中的getter可以使用可为空的包装器来提高空安全性。*如果基础投影值不是null,则使用包装器类型的当前表示返回值。如果支持值是null,则getter方法返回所用包装器类型的空表示。*当前支持的包装器类型有:*java.util.Optional、com.google.common.base.Optional、scala.Option、io.vavr.control.Option*/interfaceNamesOnly{OptionalgetFirstname();}内部原理:

2.使用自定义对象来接收结果集

返回对象为自定义的class,可定义toString方法,打印日志方便。

自定义的class须包含查询的字段的属性,且要封装的字段由公开的构造方法确定,对象属性不要求名称一致,只需要构造方法参数位置正确。

JPQL语法须为:selectnew+对象全限定类名

/***Repository自定义查询方法*/@Query(selectnewcom.xx.yy.PersonResult(p.id,p.name,p.age)fromPersonp)ListfindPersonResult();/***自定义class类来映射结果*自定义class类中属性若全为JPQL中的查询字段且顺序一致,使用@AllArgsConstructor全参构造方法即可,否则需手写相应构造方法。*/@Data@AllArgsConstructor@NoArgsConstructorpublicclassIdAndLanguageType{Stringid;Stringname;Stringage;}

3.使用Map或List来接收结果集

注意:指定为Map时,实际类型是org.springframework.data.jpa.repository.query.AbstractJpaQuery$TupleConverter$TupleBackedMap,该类型只能读不能改或写

(1)nativeQuery查询,即原生SQL查询

直接select部分字段即可,结果集默认会自动包装为Map。

/***Repository自定义查询方法*/@Query(value="selectg.id,g.school_idasschoolId,g.namefromgradeg"+"leftjoinstudentsong.name=s.grade"+"whereg.school_id=(selecta.school_idfromadminawherea.id=1)"+"and(4isnullorg.namelike%4%org.bzlike%4%)"+"groupbyg.idlimit2,3",nativeQuery=true)List>myGetGradeList(StringadminId,Integerpage,Integersize,StringsearchGradeNameOrGradeBz);(2)JPQL查询

可以手动指定包装为map,此时map的key为字段序号,故最通过as指定key为字段名。

默认会将结果包装为List而不是Map,可以手动指定包装为map,此时map的key为字段序号(0、1、2...),也可以通过as指定key为字段名。

/***Repository自定义查询方法*注意是'map',不是jdk中的'Map'!*/@Query("selectnewmap(g.nameasname,count(s.id)asstuCount)fromGradeEntityg,StudentEntitys"+"whereg.name=s.gradeandg.schoolId=1groupbyg.id")List>myGetBySchoolId(StringschoolId);@Query("selectnewmap(g.nameasname,count(s.id)asstuCount)fromGradeEntityg,StudentEntitys"+"whereg.name=s.gradeandg.schoolId=1groupbyg.id")ListmyGetBySchoolId(StringschoolId);

动态投影

/***Repository自定义查询方法*动态投影方式。泛型根据需要传入Entity实体类或封装部分字段的自定义接口*/publicinterfacePersonRepositoryextendsRepository{CollectionfindByLastname(Stringlastname,Classtype);}

count查询

IntegercountByName(Stringname);

In查询

不管是否是@Query都可以用in查询,如:

原因:运行时动态生成sql语句,如果id列表为null或空列表,则最终生成的sql语句中"whereidinnull"不符合sql语法。

1、Entity内未定义关联实体时的联表查询,示例:

@Query("selectcdfromCourseDeveloperEntitycdjoinDeveloperdwhered.nickName='stdeveloper'")2、Entity内定义的关联实体的关联查询,示例:

@Query("selectcd,dfromCourseDeveloperEntitycdjoincd.developerdwhered.nickName='stdeveloper'")||(等价于)@Query("selectcd,cd.developerfromCourseDeveloperEntitycdwherecd.developer.nickName='stdeveloper'")若将一个对象的关联对象指定为延迟加载LAZY,则每次通过该对象访问关联对象时(如courseDeveloper.developer)都会执行一次SQL来查出被关联对象,显然如果被关联对象访问频繁则此时性能差。

解决:

延迟加载与立即加载(FetchType)

默认情况下,@OneToOne、@ManyToOne是LAZY,@OneToMany、@ManyToMany是EAGER。但不绝对,看具体需要。

有两个地方用到延迟加载:relationship(@OneToMany等)、attribute(@Basic)。后者一般少用,除非非常确定字段很少访问到。

Repository方法核心方法

//添加or修改数据Ssave(Sentity)底层逻辑为:当entity的id为null,则直接新增,不为null,则先select,如果数据库存在,则update。如果不存在,则insert

注意:若JPA启用了逻辑删除(软删除)功能,使用save方法则可能会出现主键冲突或唯一索引冲突等问题

原因:若数据库启用了逻辑删除功能,记录逻辑删除后,该条记录实际仍存在于数据库中,但是JPA根据Entity的主键查询数据库判断该执行insert还是update时,查询语句会自动加上逻辑删除的判断条件,从而查不到数据而最终执行insert,进而可能会导致报主键冲突或唯一索引冲突。

update

1、删除记录

Repository接口核心方法

voiddelete(Tentity)

Repository自定义删除方法

需要加@Modefying、@Transactional

也正因为这样,软删除功能中指定@SQLDelete("updatestudentsetis_delete='Y'whereid=")即可对所有非nativeQuery起作用。

方法名包含条件的删除操作(例如IntegerdeleteByNameAndSId(Stringname,Stringuuid);),其执行时与save类似,也是先根据条件查出目标Entity再执行删除操作。对于voiddelete(Tentity);,则直接根据Entity的主键操作而不用先查。

2、逻辑删除

使用org.hibernate.annotations(不是JPA的标准)的@Where、@SQLDelete、@SQLDeleteALL三个注解来实现。

//对非nativeQuery旳delete起作用,包括形如deleteByName等,下同。@SQLDelete(sql="update"+StudentEntity.tableName+"set"+constant.ISDELETE_COLUMN_NAME+"=truewheresid=")@SQLDeleteAll(sql="update"+StudentEntity.tableName+"set"+constant.ISDELETE_COLUMN_NAME+"=truewheresid=")//对非nativeQuery的select起作用(如count、非nativeQuery的StringmyGetNameByName等,前者本质上也是select)@Where(clause=constant.ISDELETE_COLUMN_NAME+"=false")@Data@Entity@Table(name=StudentEntity.tableName)publicclassStudentEntityextendsBaseEntity{publicstaticfinalStringtableName="student";...@Column(name=constant.ISDELETE_COLUMN_NAME,nullable=false)privateBooleanisDelete=false;}需要注意的是:

关于软删除:对于关联表(一对一、一对多、多对多),若要启用软删除,则须为多对多关联表定义额外的主键字段而不能使用联合外键作为主键,否则软删除场景下删除关联关系再重新关联时会主键冲突。另外,特殊情况下多对多关联表可以不启用软删除(被关联表、一对多或多对一关联表则需要,因为它们侧重的信息往往不在于关联关系而是重要的业务信息)

原生的saveAll()方法可以保证程序的正确性,但是如果数据量比较大时效率低。

源码逻辑原理是:for循环集合调用save方法;save方法逻辑为,当entity的id为null,则直接新增,不为null,则先select,如果数据库存在,则update。如果不存在,则insert。

@TransactionalpublicListsaveAll(Iterableentities){Assert.notNull(entities,"Entitiesmustnotbenull!");Listresult=newArrayList();Iteratorvar3=entities.iterator();while(var3.hasNext()){Sentity=var3.next();result.add(this.save(entity));//save方法是核心逻辑}returnresult;}@TransactionalpublicSsave(Sentity){if(this.entityInformation.isNew(entity)){this.em.persist(entity);returnentity;}else{returnthis.em.merge(entity);}}解决方案:

优化方案为:当保存大量数据时,直接使用em进行持久化插入,省了一步查询操作。并且考虑到如果最后才提交所有数据,数据库的负载可能会比较大,故每100条记录就提交(flush)一次。

@AutowiredprivateEntityManagerentityManager;privatefinalintBATCH_SIZE=1000;@Transactional(rollbackFor=Exception.class)publicvoidaddBatch(Listlist){intnum=0;for(Ss:list){entityManager.persist(s);//insert插入操作(变成托管状态)intnum+=1;if(i%BATCH_SIZE==0){entityManager.flush();//变成持久化状态entityManager.clear();//变成游离状态}}}在确保数据已经存在的情况下,如果是批量更新可以如下代码代替上面的entityManager.persist(projectApplyDO);语句:

entityManager.merge(projectApplyDO);//update更新操作

JPA事务内Entity变更会自动更新到数据库

若启用了事务,则对于managed状态的entity,若在事务内该entity有字段的值发生了变化,则即使未调save方法,该entity的变化最后也会被自动同步到数据库,即sqlupdate操作。即相当于在PersistContextflush时自动对各engity执行save方法。(org.hibernate.event.internal.AbstractFlushingEventListener中)

1.写一个与Repository接口同名的类,加上后缀Impl,标注@Repository注解;这个类不需要实现任何接口,可以自动被扫描到。

2.在Repository接口中加入自定义的方法,比如:

publicinterfaceMyRepositoryextendsJpaRespository{//自定义的方法publicPagegetByCondition(UserQueryModelu);}3.在实现类中,去实现在Repository接口中加入的自定义方法,会被自动找到

@RepositorypublicclassMyRepositoryImpl{@AutowiredprivateEntityManagerem;//实现在Repository接口中加入的自定义方法publicPagegetByCondition(UserQueryModelu){Stringhql="selecto.uuid,o.namefromUserEntityowhere1=1ando.uuid=:uuid";Queryq=em.createQuery(hql);q.setParameter("uuid",u.getUuid());q.setFirstResult(0);q.setMaxResults(1);Pagepage=newPageImpl(q.getResultList(),newPageRequest(0,1),3);returnpage;}}

Repository方式调用存储过程需要基于Entity实体类,在实体类上使用@NamedStoredProcedureQuery注解(需要数据库中有对应的表,可自动映射结果集)。详解HibernateEntityManger专题-JPA调用存储过程条目。

importjava.util.List;importorg.springframework.data.jpa.repository.JpaRepository;importorg.springframework.data.jpa.repository.query.Procedure;importorg.springframework.data.repository.query.Param;importorg.springframework.stereotype.Repository;importcom.labofjet.entity.A;importcom.labofjet.entity.APK;@RepositorypublicinterfaceARepositoryextendsJpaRepository{//方式1。若用这种方式,方法名要与存储过程名一样。【推荐】@ProcedureIntegerplus1inout(Integerarg);@ProcedureObject[]mytest();//方式2。Procedure的name为实体类上@NamedStoredProcedureQuery注解中name的值@Procedure(name="User.plus1")Integeralias2(@Param("arg")IntegerargAlias);//@Param必须匹配@StoredProcedureParameter注释的name参数//方式3。Procedure的procedureName参数必须匹配实体类上@NamedStoredProcedureQuery的procedureName的值@Procedure(procedureName="plus1inout")Integeralias3(Integerarg);}注意:返回类型必须匹配。in_only类型的存储过程返回是void,in_and_out类型的存储过程返回相应数据类型

springdatajpa提供了JpaSpecificationExecutor接口,只要简单实现toPredicate方法就可以实现复杂的动态查询。

Specification是Spring对Criteria的封装。

JpaSpecificationExecutor提供了以下接口

publicinterfaceJpaSpecificationExecutor{TfindOne(Specificationspec);ListfindAll(Specificationspec);PagefindAll(Specificationspec,Pageablepageable);ListfindAll(Specificationspec,Sortsort);longcount(Specificationspec);}//其中Specification就是需要我们传入查询方法的参数,它是一个接口publicinterfaceSpecification{PredicatetoPredicate(Rootroot,CriteriaQuery<>query,CriteriaBuildercb);//root:根参数,代表了可以查询和操作的实体对象的根,如果将实体对象比喻成表名,那root里面就是这张表里面的字段,是JPQL的实体字段,通过Pathget(Stringvar0)来获得操作的字段//criteriaQuery:代表一个specific的顶层查询对象,它包含着查询的各个部分,如:select、form、where、groupby、orderby等,它提供了查询的的方法,常用的有where、select、having//criteriaBuilder:用来构建CriteriaQuery的构建器对象,其实就相当于条件或条件组合}提供唯一的一个方法toPredicate,我们只要按照JPA2.0criteriaapi写好查询条件就可以了。

关于JPA2.0criteriaapi的介绍和使用,欢迎参考:

Repository继承JpaSpecificationExecutor接口

publicinterfaceTaskResposityextendsJpaRespository,JpaSpecificationExecutor{}

调用

publicclassTaskSpec{//封装查询条件的静态方法publicstaticSpecificationmethod1(Tasktask){returnnewSpecification(){@OverridepublicPredicatetoPredicate(Rootroot,CriteriaQuery<>query,CriteriaBuildercb){//示例,未写具体的查询条件,入参从task中获取returnnull;}};}}//使用Pagetasks=this.taskDao.findAll(TaskSpec.method1(),pageReq);

THE END
1.起名软件免费下载起名app官方版免费起名软件手机版取名app哪个好?免费男孩女孩起名大全是本站小编专为有需要的宝爸宝妈准备的一些好用的给宝宝取名字的软件,我们都知道这名字一经敲定以后正常情况下都会伴随我们一生的,孩子将顶着这一个名字过一生!接下来本小编为你们推荐一些好用的取名字的软件,可以来当易网下载使用哦!http://www.downyi.com/key/qimingapp/
2.名字随机生成器——让你快速取名在生活中,取名是一项非常重要的任务,名字的好坏将会对你的生活产生很大的影响。但是取好名字并不是一件容易的事情,有的人会花费很长时间才能想出一个满意的名字。为了解决这一问题,今天我们就来介绍一种名字生成器——名字随机生成器。 名字随机生成器是一款非常实用的工具,可以帮助你快速生成适合你的名字。该工具https://www.bamuwu.com/details/2064
3.手机上免费的起名软件排行榜前十名偏玩手游盒子>手机上免费的起名软件合辑手机上免费的起名软件排行榜TOP10下载给宝宝取名字是非常有讲究的,家长都希望给自己的宝宝取一个具有意义的名字,但是绝大部分家长对于怎么给宝宝取名是比较迷茫的,这个时候可以试试手机上免费的起名软件,为宝宝取一个好名字。https://m.pianwan.com/s/zj-2067471
4.在线取男生名字大全男宝宝姓名生成器艺术字在线取男生名字大全 免费男宝宝姓名生成器 取姓名生成器 在线取男生名字大全,免费男宝宝姓名生成器。 在线取男生名字大全,内置两百万姓名名字。随机姓氏支持复姓、少数名族姓氏,满足个人取名字、战队队员取名字、家族生成名字、小说人物取名等名字在线生成需要!http://www.yishuzi.com/g/m9.htm
5.起名工具,免费在线起名,在线取名字,生成中文名字在线免费取名工具,在线免费起名,帮你取一个好名字,一个好名字随人一生,能照亮一个人的生活、还能辉煌一个人的事业,在线起名工具,生成好名字https://qvdv.net/tools/qvdv-quming.html
6.姓名生成器男生女生姓名生成小工具宝宝取名生成器名字长度 生成数量 输入姓氏 开启生成 关于名字生成器介绍: 1、本工具可以随机生成多个名子供参考。 2、输入要生成姓氏,支持复姓。 3、输入名子的长度,最短为2个字,最长为3个字,官方规定的是3个字。 4、输入生成的数量,多以批量生成多个供参考。http://www.gjtool.com/tool/names.html
7.网名生成器姓名生成器名字在线生成器网名在线生成支持在线网名、姓名、游戏名字、家族团体名字批量生成,支持性别选择,支持姓氏指定,支持随机姓氏!https://name.gayuseal.com/
8.网名生成器姓名生成器名字在线生成器网名在线生成支持在线网名、姓名、游戏名字、家族团体名字批量生成,支持性别选择,支持姓氏指定,支持随机姓氏!https://www.qmsjmfb.com/
9.免费取名男孩女孩取名取名软件在线给孩子取名在线名字生成器 你只要在上面的输入框中输入或者粘贴入你的姓氏,如“周”,然后点男名或女名按钮生成选取你想要的名字和姓名! 内含五百多个姓氏,可免费生成八百万名字和30亿含姓氏姓名。 姓名学渊源于我国古代诸多先贤的哲学思想,是我国的国粹。孔子说:“名不正则言不顺”。苏东坡说:“世间唯名实不可欺”。严复https://m.appgg.com/zxmz.html
10.数据测试日记名字生成器在线名字生成自助姓名生成器名字生成器是一款免费的自助起名工具,输入姓氏即可随机生成指定姓氏的名字,姓氏为空或无法识别会生成随机姓氏的名字,可重复点击生成。 功能导航 五行名字生成器诗词名字生成器八字名字生成器叠字名字生成器小名生成器四字名字生成双胞胎名字生成器定字生成名字英文名字生成器唐诗起名宋词起名诗经起名楚辞起名起单字名名字测试https://www.bzcm88.com/
11.在线随机中文姓名生成器在线随机中文姓名生成器,可以随机生成大量中文姓名,支持指定男女性别,支持导出到TXT中。https://www.sojson.com/document/rname.html
12.男宝宝姓名生成器急切网列表一元二次方程取名字生成器取姓名生成器 免费男宝宝姓名生成器_在线取男生名字大全 指定姓氏 性别 在线男宝宝姓名生成器。 在线取男生名字大全,内置两百万姓名名字。随机姓氏支持复姓、少数名族姓氏,满足个人取名字、战队队员取名字、家族生成名字、小说人物取名等名字在线生成需要!http://75.84c.cn/5/m23.htm
13.起名工具,免费在线起名,在线取名字,生成中文名字在线免费取名工具,在线免费起名,帮你取一个好名字,一个好名字随人一生,能照亮一个人的生活、还能辉煌一个人的事业,在线起名工具,生成好名字https://www.qvdv.com/tools/qvdv-quming.html
14.小说人名生成器小说人名生成器选取常见的好听名字用字,可以随机生成极为好听的男女主名字,用作为小说人物的名字、网游角色名字等都是极好的。 小说人名生成结果(男) 姓氏 AI版小说人名生成器 升沛杞嘉轩皓起哲炳涛斌良坤起睿浩星鹏涛初诚强远凡腾初槐濡桓杞柏坤吉鑫锋俊尧涛琛韦强凡逸贤胤翰子哲吉驰哲权锦帝烁晨家濡澄胤https://www.resgain.net/novel/index.html?fm=1&nums=240
15.起名免费网输入姓氏自动取名免费免费名字生成器 智能起名姓名打分五行起名定字起名生肖取名重名查询 男女 立即智能起名 九大维度免费起名,1000万精选名字库。 男女 立即姓名打分 综合测试名字音律字型与传统国学运势得分。 男女 金木水土火 立即五行起名 根据名字金木水火土属性,免费批量生成名字。 https://www.mumingwang.com/
16.连氏起名大全女孩高雅有涵养的名字3、取名自动生成器 4、三字顺口名字公司 5、别墅私宅门头取名 6、连氏家谱 2023男宝宝取名大全2023宝宝取名大全属相运势,2023男宝宝取名大全2023宝宝取名大全2023年属蛇的男孩起什么名字属蛇的男孩取什么名字好提起2023年属蛇的男孩起什么名字大家都知道有人问属蛇的男孩取什么名字好另外还有人想问2023年属蛇。 https://www.16757.com/ysh/xingming/4061.html
17.语音自动生成器免费版自动对联生成器?语音自动生成器免费版 自动对联生成器? 一、对联自动生成器? 微软对联生成器是一款非常不错的对联生成工具。由微软亚洲研究院自然语言计算组研发的计算机自动对联系统。 首先用户给定上联,然后系统自动提供若干下联供用户选择, 用户可以通过交互手段优选字词来生成满意的下联;当确定一副对联后还可以生成若干四字横批供https://tool.a5.cn/article/show/75582.html
18.Python实验上海交通大学上海交大python教程-P 若远程端口号不是22,则需要使用大些字母 -P 选项指定端口。 SSH高级 免密码登录 配置别名 提示:有关SSH配置信息都保存在用户家目录下的.ssh目录下 免密码登录: 配置公钥 执行ssh-keygeb 即可生成钥匙,一路回车即可 上传公钥到服务器 执行ssh-copy-id -p port user@remote,可以让远程服务器记录我们的公钥https://blog.51cto.com/u_13019/10938729
19.2.CSS&CSS3这里略过 老旧的ie盒子模型(IE6以下),对不起,我都没见过IE5的浏览器。 。首先,我们来看一张图,来体会下什么是盒子模型。 所有的文档元素(标签)都会生成一个矩形框,我们成为元素框(element box),它描述了一个文档元素再网页布局汇总所占的位置大小。因此,每个盒子除了有自己大小和位置外,还影响着其他盒子的https://www.jianshu.com/p/bc77ce47636e
20.公司起名生成器公司取名常用字企业取名公司核名公司起名生成器 嵊州市免费公司起名、晋城市免费公司名字、乐清市免费公司取名、宁德市免费企业名字、安庆市免费公司取名字、周口市免费企业取名字、乌鲁木齐市免费企业名字、明光市免费企业名字、慈溪市免费公司取名字、常州市免费企业名字、商洛市免费公司取名、慈溪市免费公司取名、梧州市免费企业取名、吴江市免费企业取名https://www.qierge.com/news/11067.html
21.刘宝宝起名最好的网站,刘的谐音字寓意好取名字2.百分起名网百分起名网是一个自动取100分名字的网站,结合宝宝生辰和音律、字形、寓意等综合评分生成100分宝宝名字。该网站还提供诗词起名、周易起名、稀有名字起名等多种工具,帮助用户轻松取个好名字。 3.美名轩美名轩是一个免费的网上在线取名字自动生成器,可以免费在线生成宝宝名字、小说人名、公司名字等。它根据大数https://www.qiyuange.com/qiming/283235.html
22.姓名自动生成器输入姓选性别***给孩子取名字不用查字典了***姓名自动生成器【输入姓选性别】***给孩子取名字不用查字典了*** 姓名自动生成器【输入姓选性别】http://www.360doc.com/content/13/1007/12/6954561_319566531.shtml
23.输入关键字生成对联“对联生成器”生成的对联生硬、机械、字与字硬对,没有灵魂、没有意境。 免费用名字作诗软件输入网名自动生成图片。 免费用名字作诗e68a847af334的软件名字作诗软件、蓝梦名字作诗软件、安酷藏头诗软件、名字作诗生成器 、财官姓名学藏头诗软件等这些都可以。 https://blog.csdn.net/weixin_39938724/article/details/111863801
24.和平精英二字ID生成器,吃鸡二字id免费在线生成器和平精英二字ID生成器,吃鸡二字id免费在线生成器,可以为您随机生成吃鸡二字ID,生成后直接复制进游戏即可修改使用,数据库包含了大量汉字,确保能生成别人没有使用过的和平精英游戏ID。 如何修改自己的和平精英游戏ID?登陆游戏,打开"仓库",点击"道具",如果您有改名卡,就可以点击使用改名卡修改您的游戏ID了。 http://www.8882088.com/idcreate/
25.字节上岸成功,整理一波测试开发岗的基础知识,含答案免费开源、可移植 自动内存管理,让程序员可以专注于代码的实现 缺点: 他的可解释特征使其运行速度变慢 动态语言的特点可能会增加运行时错误。 装饰器 装饰器本质上是一个Python函数,它可以让其他函数在不需要做任何代码变动的前提下增加额外功能,它接受一个函数作为参数,并返回一个函数,利用python的@语法来放置 https://maimai.cn/article/detail?fid=1679089730&efid=YLyF0ejo0qEtjVB1UUE-yw
26.在线作诗机一键生成爱情藏头诗藏头诗在线生成器艺术字体设计网藏头诗网在线为您提供在线藏头诗,藏头诗在线制作,姓名藏头诗,爱情表白藏头诗生成器,在线作诗网,在线网名名字作诗,想向自己心爱的人表白,但又不太好意思,这时可以把自己要说的话做成一首诗,再送给她,让她感觉到你的心意。这就是现在很流行的爱情表白藏http://cts.qt86.com/
27.www.wqwl168.com/gonode75311833.html电影网站日韩夜生活免费看一区二区三区四区 73.74MB 8539好评 埃及猫人类拔萝卜的电影名字 六月女上位全自动榨精肤白 49.37MB 59好评 欧美亚洲第一色色色 80岁免费看手机 精品无码久久久久久毛毛logo图片生成器欧美肥婆ua 85.23MB 11%好评5371人) 一个人在线看的 999精品视频在这里只有 http://www.wqwl168.com/gonode75311833.html
28.公司取名生成器,适合公司起名的寓意字,公司名算命汉斯 拓中 圣尔 贸识 铭立 集领 康维 清赛 日发 典迈 火太 腾基 跃大 旋华 洋克 先星 网海 南木 浩川 华缘 速华 丰仕 洋方 系精 苏汉 隆茂 银翔 白方 华建 本玛 明瑞 健扬 尼磊 新贸 维洲 飞玉 创浩 公司取名生成器(三字名推荐): http://www.zhouyiruanjian.com/html/2755.htm
29.免费logo设计字体商标标志在线制作在线公司logo生成器Logo一键生成器 无需很长设计周期,logo设计在线生成 输入品牌名称,立即开始。 开始生成 logo设计网,为公司,个人,店铺,品牌VI,网站,网店提供商标logo自动在线一键生成,标志艺术字体设计,文字图标图片设计,字母头像制作,姓名字免费lougo设计,创意logo制作,英文loog,无水印透明logo,可自己下载svg源文件. https://www.shejilogo.com/
30.女孩取名免费的有吗另一种免费的女孩取名资源是在线取名生成器。这些生成器可以根据家长输入的条件和喜好,自动生成一系列与其要求匹配的名字。家长可以根据自己的需求调整条件,例如:希望名字以特定字母开头、包含特定字母或特定音调等。生成器会根据这些条件生成一系列名字供家长选择。 https://www.qq772.com/text/52942.html
31.中文字笔画顺序生成田字格字体笔顺生成器www.bihua123.cn7年老牌中文笔画顺序生成,田字格字帖生成工具,可以在线生成田字格字帖、米字格、回宫格字帖,支持任意汉字定制,并支持笔顺,支持田英章字体、庞中华字体,田字格作是最为精简的练习格,被小学低年级语文课本作为生字词的展示方式。小学时期,特别是低年级阶段,是学习汉字https://www.bihua123.cn/
32.免费在线起英文名按首字母音译和意译起英文名起英文名美名腾智能起名网的免费在线起英文名,是一个深受用户喜欢的超酷在线起英文名实用工具。能够根据中文名字的读音起英文名,也能够根据中文名字的意义起英文名,还能够按照首字母起英文名。 与美名腾的其它起名模块,如宝宝起名一样,它不是一个简单的名字查询或搜索匹配,而是一个根据用户的中文名字,性别和个性意愿真正起https://www.meimingteng.com/Message/ShowNews.aspx?NewsID=157
33.6.表达式—Python3.13.1文档当下层迭代器完成时,被引发的 StopIteration 实例的 value 属性会成为 yield 表达式的值。 它可以在引发 StopIteration 时被显式地设置,也可以在子迭代器是一个生成器时自动地设置(通过从子生成器返回一个值)。 在3.3 版本发生变更: 添加yield from <expr> 以委托控制流给一个子迭代器。 当yield表达式是赋值语句http://docs.python.org/zh-cn/3.13/reference/expressions.html