Hibernate

总览

Hibernate是一种用于Java环境的对象/关系映射(ORM)解决方案。有效地“位于” Java应用程序数据访问层和关系数据库之间。

1. 领域模型

Hibernate实现Java Persistence API规范。使用Hibernate的应用程序会为此目的使用其专有的XML映射文件格式。随着JPA的到来,现在大多数信息都以注解(和/或标准化XML格式)可在ORM / JPA提供程序之间移植的方式进行定义。对于JPA不支持的Hibernate映射功能,我们将更喜欢Hibernate扩展注解。

1.1. 映射类型

Hibernate将类型分为两类:值类型、实体类型

SQL:
create table Contact (id integer not null,first varchar(255),last varchar(255),middle varchar(255),notes varchar(255),starred boolean not null,website varchar(255),primary key (id)
)Java:
@Entity(name = "Contact")
public static class Contact {@Idprivate Integer id;private Name name;private String notes;private URL website;private boolean starred;//Getters and setters are omitted for brevity
}@Embeddable
public class Name {private String first;private String middle;private String last;// getters and setters omitted
}

1.1.1. 值类型

值类型进一步分为三个子类别:基本类型、可嵌入类型(例如上述例子中的Name)、集合类型

1.1.2. 实体类型

实体根据其唯一标识符的性质独立于其他对象而存在,而值则不存在。实体是域模型类,使用唯一标识符与数据库表中的行相关。由于需要唯一标识符,因此实体独立存在并定义自己的生命周期。例如上述例子中的Contact。

1.2. 命名策略

从Hibernate5开始,从领域模型到关系型数据库的名称映射分成两个步骤:

  1. 从域模型映射中确定适当的逻辑名。逻辑名可以由用户明确指定(例如使用@Column或 @Table),也可以由Hibernate通过ImplicitNamingStrategy协定隐式确定。
  2. 将此逻辑名称解析为由PhysicalNamingStrategy合同定义的物理名称。

隐式命名配置:

hibernate.implicit_naming_strategy:
- default //jpa的别名
- jpa // jpa2.0标准的命名策略
- legacy-hbm // 兼容旧版本的命名策略
- legacy-jpa // jpa1.0规范
- coponent-path //类似default,区别在于采用全路径
- 可以指定一个继承ImplicitNamingStrategy的类

物理命名策略是用来把逻辑名称转换成在数据库中的具体表名或列名,默认情况下直接返回逻辑名称。如果需要在表名或者列名前加前缀,例如逻辑名称为account的逻辑名称需要映射到tab_account的表名时,可以继承PhysicalNamingStrategy来自定义实现,并在该属性 hibernate.implicit_naming_strategy 指定自定义的类。

1.3. 基本类型

基本值类型通常将单个数据库列映射到单个非聚合Java类型,
点此查看官网列出的基本类型。org.hibernate.type.BasicTypeRegistry是用来管理各种基本类型类,管理一个以注册名称为key,BasicType为value的Map对象。通过这个对象hibernate才能准确的将java类型映射成sql类型。

JPA规范严格将可以标记为基本的Java类型限制在以下列表中,如果需要提供程序的可移植性,则应仅遵循这些基本类型:

  • Java基本类型(boolean,int,等)
  • 包装的原始类型(java.lang.Boolean,java.lang.Integer等)
  • java.lang.String
  • java.math.BigInteger
  • java.math.BigDecimal
  • java.util.Date
  • java.util.Calendar
  • java.sql.Date
  • java.sql.Time
  • java.sql.Timestamp
  • byte[] or Byte[]
  • char[] or Character[]
  • enums
  • 实现的任何其他类型Serializable(JPA对Serializable类型的“支持” 是将其状态直接序列化到数据库)。

@Column(name="…") 指定列名
@Entity(name="…") 指定表名

显示指定基本类型
如果需要对特定属性进行不同的类型处理,需要加上注解@org.hibernate.annotations.Type(type = …), type可以是:

  • 任何实现org.hibernate.type.Type的全限定名称
  • 任何在BasicTypeRegistry已注册的key值
  • 任何已知类型定义的名称
@Entity(name = "Product")
public class Product {@Idprivate Integer id;private String sku;@org.hibernate.annotations.Type( type = "nstring" )private String name;@org.hibernate.annotations.Type( type = "materialized_nclob" )private String description;
}

1.3.1. 自定义基本类型

有两种方式可以自定义一个基本类型:

  • 实现BasicType接口(或者AbstractStandardBasicType,AbstractSingleColumnStandardBasicType)并注册类型
  • 实现UserType接口(无需注册)

一、实现AbstractSingleColumnStandardBasicType接口,
定义一个BitSetType,用来完成从Java类型BitSet到SQL类型VARCHAR的映射:

public class BitSetType extends AbstractSingleColumnStandardBasicType<BitSet> implements DiscriminatorType<BitSet> {public static final BitSetType INSTANCE = new BitSetType();public BitSetType() {super( VarcharTypeDescriptor.INSTANCE, BitSetTypeDescriptor.INSTANCE );}@Overridepublic BitSet stringToObject(String xml) throws Exception {return fromString( xml );}@Overridepublic String objectToSQLString(BitSet value, Dialect dialect) throws Exception {return toString( value );}@Overridepublic String getName() {return "bitset";}}

这里面的构造方法里需要两个类,属于 sqlTypeDescriptor 的 VarcharTypeDescriptor 和 javaTypeDescriptor BitSetTypeDescriptor,需要自己实现用于BitSet的 javaTypeDescriptor。

public class BitSetTypeDescriptor extends AbstractTypeDescriptor<BitSet> {private static final String DELIMITER = ",";public static final BitSetTypeDescriptor INSTANCE = new BitSetTypeDescriptor();public BitSetTypeDescriptor() {super( BitSet.class );}@Overridepublic String toString(BitSet value) {StringBuilder builder = new StringBuilder();for ( long token : value.toLongArray() ) {if ( builder.length() > 0 ) {builder.append( DELIMITER );}builder.append( Long.toString( token, 2 ) );}return builder.toString();}@Overridepublic BitSet fromString(String string) {if ( string == null || string.isEmpty() ) {return null;}String[] tokens = string.split( DELIMITER );long[] values = new long[tokens.length];for ( int i = 0; i < tokens.length; i++ ) {values[i] = Long.valueOf( tokens[i], 2 );}return BitSet.valueOf( values );}@SuppressWarnings({"unchecked"})public <X> X unwrap(BitSet value, Class<X> type, WrapperOptions options) {if ( value == null ) {return null;}if ( BitSet.class.isAssignableFrom( type ) ) {return (X) value;}if ( String.class.isAssignableFrom( type ) ) {return (X) toString( value);}throw unknownUnwrap( type );}public <X> BitSet wrap(X value, WrapperOptions options) {if ( value == null ) {return null;}if ( String.class.isInstance( value ) ) {return fromString( (String) value );}if ( BitSet.class.isInstance( value ) ) {return (BitSet) value;}throw unknownWrap( value.getClass() );}
}

unwrap传递时方法用于将BitSet作为PreparedStatement绑定参数,而wrap方法用于将JDBC列值转变为实际的映射对象类型。可以在应用初始化时执行以下代码注册类型。

configuration.registerTypeContributor( (typeContributions, serviceRegistry) -> {typeContributions.contributeType( BitSetType.INSTANCE );
} );

实际使用时:

@Type(type = "bitset")
private BitSet bitSet;

使用注解@TypeDef就可以省略注册的过程,直接使用:

@Entity(name = "Product")
@TypeDef(name = "bitset",defaultForType = BitSet.class,typeClass = BitSetType.class
)
public static class Product {@Idprivate Integer id;private BitSet bitSet;//Getters and setters are omitted for brevity
}

二、实现UserType接口

public class BitSetUserType implements UserType {public static final BitSetUserType INSTANCE = new BitSetUserType();private static final Logger log = Logger.getLogger( BitSetUserType.class );@Overridepublic int[] sqlTypes() {return new int[] {StringType.INSTANCE.sqlType()};}@Overridepublic Class returnedClass() {return BitSet.class;}@Overridepublic boolean equals(Object x, Object y)throws HibernateException {return Objects.equals( x, y );}@Overridepublic int hashCode(Object x)throws HibernateException {return Objects.hashCode( x );}@Overridepublic Object nullSafeGet(ResultSet rs, String[] names, SharedSessionContractImplementor session, Object owner)throws HibernateException, SQLException {String columnName = names[0];String columnValue = (String) rs.getObject( columnName );log.debugv("Result set column {0} value is {1}", columnName, columnValue);return columnValue == null ? null :BitSetTypeDescriptor.INSTANCE.fromString( columnValue );}@Overridepublic void nullSafeSet(PreparedStatement st, Object value, int index, SharedSessionContractImplementor session)throws HibernateException, SQLException {if ( value == null ) {log.debugv("Binding null to parameter {0} ",index);st.setNull( index, Types.VARCHAR );}else {String stringValue = BitSetTypeDescriptor.INSTANCE.toString( (BitSet) value );log.debugv("Binding {0} to parameter {1} ", stringValue, index);st.setString( index, stringValue );}}@Overridepublic Object deepCopy(Object value)throws HibernateException {return value == null ? null :BitSet.valueOf( BitSet.class.cast( value ).toLongArray() );}@Overridepublic boolean isMutable() {return true;}@Overridepublic Serializable disassemble(Object value)throws HibernateException {return (BitSet) deepCopy( value );}@Overridepublic Object assemble(Serializable cached, Object owner)throws HibernateException {return deepCopy( cached );}@Overridepublic Object replace(Object original, Object target, Object owner)throws HibernateException {return deepCopy( original );}
}

1.3.2. 枚举类型映射

最初的JPA兼容映射枚举方法是通过 @Enumerated 或 @MapKeyEnumerated 映射键注解进行的。EnumType.ORDINAL 表示会把枚举类中枚举值的序数位置存进数据库(从0开始),EnumType.STRING 表示会把枚举类型名称存进数据库,如果是空值,都是存NULL。

@Enumerated(EnumType.ORDINAL 或者 EnumType.STRING)
@Column(name = "phone_type")
private PhoneType type;

如果要满足以下这种情况,把 Gender 枚举类型的 code 值存进数据库,需要使用 AttributeConverter。

public enum Gender {MALE( 'M' ),FEMALE( 'F' );private final char code;...
}
@Entity(name = "Person")
public class Person {@Convert( converter = GenderConverter.class )public Gender gender;...
}
@Converter
public class GenderConverterimplements AttributeConverter<Gender, Character> {public Character convertToDatabaseColumn( Gender value ) {if ( value == null ) {return null;}return value.getCode();}public Gender convertToEntityAttribute( Character value ) {if ( value == null ) {return null;}return Gender.fromCode( value );}
}

也可以通过自定义类型的定义一个GenderType的类型,在unwrap和wrap方法里处理数据库类型和java类型的转换。

1.3.3. 映射 LOB

1.3.3.1. 映射CLOB

使用 @Lob 注解和 java.sql.Clob 类型来映射(也可以用String 或 char[])。

@Entity(name = "Product")
public static class Product {@Idprivate Integer id;private String name;@Lobprivate Clob warranty;//Getters and setters are omitted for brevity}

保存Clob类型:

String warranty = "My product warranty";
final Product product = new Product();
product.setId( 1 );
product.setName( "Mobile phone" );
product.setWarranty( ClobProxy.generateProxy( warranty ) );
entityManager.persist( product );

读取Clob: Reader reader = product.getWarranty().getCharacterStream()

1.3.3.2. 映射BLOB

和CLOB类似,使用Blob类型映射(或byte[])。使用BlobProxy存储。

1.3.4. 时间类型的映射

SQL定义了三种标准日期类型:1. DATE, 2. TIME, 3. TIMESTAMP
@Temporal注解表名sql时间映射类型,使用 java.util.Date (java.util.Calendar也可以)来映射事件类型:

  1. 映射成 DATE(年月日):
     @Column(name = "`time`")@Temporal(TemporalType.DATE)private Date time;
    
  2. 映射成 TIME(时分秒):
     @Column(name = "`time`")@Temporal(TemporalType.TIME)private Date time;
    
  3. 映射成 TIME(年月日 + 时分秒):
     @Column(name = "`time`")@Temporal(TemporalType.TIMESTAMP)private Date time;
    

如果未指定时区,则JDBC驱动程序将使用底层的JVM默认时区。

1.3.5. @Generated

使用@Generated注解是为了使Hibernate在持久化或更新实体后,可以获取当前注解的属性。@Generated注解接受一个GenerationTime枚举值指定数据库什么时候应该生成该属性的值。

  1. NEVER (默认),给定的属性值不在数据库内生成。

  2. INSERT,给定的属性值在插入时生成,但在后续更新中不会重新生成。诸如creationTimestamp之类的属性都属于此类。

  3. ALWAYS,属性值是在插入和更新时生成的。

示例:

@Entity(name = "Person")
public static class Person {@Idprivate Long id;private String firstName;private String middleName;private String lastName;@Generated( value = GenerationTime.ALWAYS )@Column(columnDefinition ="AS CONCAT(" +"	COALESCE(firstName, ''), " +"	COALESCE(' ' + middleName, ''), " +"	COALESCE(' ' + lastName, '') " +")")private String fullName;
}

1.3.6. @GeneratorType

@GeneratorType 用自定义类来处理被注解属性的值。接受两个参数 @GeneratorType( type = ?.class, when = GenerationTime.?)

public static class CurrentUser {public static final CurrentUser INSTANCE = new CurrentUser();private static final ThreadLocal<String> storage = new ThreadLocal<>();public void logIn(String user) {storage.set( user );}public void logOut() {storage.remove();}public String get() {return storage.get();}
}public static class LoggedUserGenerator implements ValueGenerator<String> {@Overridepublic String generateValue(Session session, Object owner) {return CurrentUser.INSTANCE.get();}
}@Entity(name = "Person")
public static class Person {@Idprivate Long id;private String firstName;private String lastName;@GeneratorType( type = LoggedUserGenerator.class, when = GenerationTime.INSERT)private String createdBy;@GeneratorType( type = LoggedUserGenerator.class, when = GenerationTime.ALWAYS)private String updatedBy;}

1.3.7. @ValueGenerationType

@ValueGenerationType 这是一种声明生成的属性或定制生成器的新方法,可以自己定义如何生成值,也可以利用数据库生成值。需要把实际的生成逻辑添加到实现AnnotationValueGeneration接口的类中。

@Entity(name = "Event")
public static class Event {@Id@GeneratedValueprivate Long id;@Column(name = "`timestamp`")@FunctionCreationTimestampprivate Date timestamp;//Constructors, getters, and setters are omitted for brevity
}@ValueGenerationType(generatedBy = FunctionCreationValueGeneration.class)
@Retention(RetentionPolicy.RUNTIME)
public @interface FunctionCreationTimestamp {}public static class FunctionCreationValueGenerationimplements AnnotationValueGeneration<FunctionCreationTimestamp> {@Overridepublic void initialize(FunctionCreationTimestamp annotation, Class<?> propertyType) {}/*** Generate value on INSERT* @return when to generate the value*/public GenerationTiming getGenerationTiming() {return GenerationTiming.INSERT;}/*** Returns null because the value is generated by the database.* @return null*/public ValueGenerator<?> getValueGenerator() {return null;}/*** Returns true because the value is generated by the database.* @return true*/public boolean referenceColumnInSql() {// 表明使用 getDatabaseGeneratedReferencedColumnValue() 方法的值return true;}/*** Returns the database-generated value* @return database-generated value*/public String getDatabaseGeneratedReferencedColumnValue() {return "current_timestamp";}
}

如果要在程序里自动生成值,即getValueGenerator, 参考 GeneratedValueGeneration 和 UpdateTimestampGeneration 的实现,也可以自定义一个 ValueGenerator

1.3.8. 列转换器

@Columnns 指定该属性可以用于多个列。
@ColumnTransformer 为指定列进行数据处理

@Columns(columns = {@Column(name = "money"),@Column(name = "currency")
})
@ColumnTransformer(forColumn = "money",read = "money / 100",write = "? * 100"
)
private MonetaryAmount wallet;

@Formula
指定该变量值由其他属性值计算得来。

private Double credit;private Double rate;@Formula(value = "credit * rate")
private Double interest;

1.3.9. 保留字

保留字在用作列名表名时需要加上引号。

@Column(name = "\"name\"")
private String name;

也可以配置 globally_quoted_identifiers 属性,设置全局保留字的引号处理。

<propertyname="hibernate.globally_quoted_identifiers"value="true"
/>

1.4. 可嵌入类型

略。

1.5. 实体类型

JPA 2.1规范的第2.1节“实体类” 定义了其对实体类的要求。希望在JPA提供者之间保持可移植性的应用程序应遵守以下要求:

  • 实体类必须带有javax.persistence.Entity注解(或在XML映射中这样表示)。
  • 实体类必须具有公共或受保护的无参数构造函数。它还可以定义其他构造函数。
  • 实体类必须是顶级类。
  • 枚举或接口不能指定为实体。
  • 实体类不能 final。实体类的任何方法或持久实例变量都不得为 final。
  • 如果实体实例要作为分离对象远程使用,则实体类必须实现该Serializable接口。
  • 抽象类和具体类都可以是实体。实体可以扩展非实体类以及实体类,并且非实体类可以扩展实体类。
  • 实体的持久状态由实例变量表示,实例变量可以对应于JavaBean样式的属性。实例变量只能由实体实例本身直接从实体的方法内部访问。客户只能通过实体的访问器方法(getter / setter方法)或其他业务方法来使用实体的状态。

但是,Hibernate的要求并不严格。与上面列表的区别包括:

  • 实体类必须具有无参数构造函数,该构造函数可以是公共的,受保护的或程序包可见性。它还可以定义其他构造函数。
  • 实体类不必是顶级类。
  • 从技术上讲,Hibernate可以持久化最终类或具有最终持久性状态访问器(getter / setter)方法的类。但是,通常不是一个好主意,因为这样做会阻止Hibernate生成用于延迟加载实体的代理。
  • Hibernate并不限制应用程序开发人员公开实例变量并从实体类本身之外引用它们。然而,这种范例的有效性是有争议的。

1.5.1. final 类

Hibernate的主要功能是可以通过运行时代理延迟加载某些实体实例变量(属性)。此功能取决于实体类是否为非最终类,或者取决于实现声明所有属性获取器/设置器的接口。为了性能调整,应该避免将持久属性以及对应的 getter 和 setter 声明为 final。

1.5.2. 无参构造函数

实体类应具有无参数的构造函数。在Hibernate里,如果您希望利用运行时代理生成即使用懒加载,则应至少使用包可见性来定义构造函数。

1.5.3. 声明持久性属性的获取器和设置器

Hibernate 建议遵循 JavaBean 约定并为实体持久属性定义 getter 和 setter。同样,如果希望使用运行时代理生成进行延迟加载,则 getter / setter 方法应至少授予对程序包可见性(default)的访问权限。

1.5.4. 映射实体

映射实体的主要部分是javax.persistence.Entity注解。
该@Entity注解定义只是name它是用来给一个特定的实体名称为JPQL查询中使用的属性。缺少 name 则使用隐式命名规则。

一般实体名就是表名,如果要显示指明表名和实体名的关系,可以使用@Table和@Entity 一起注解来说明:

@Entity(name = "Book")
@Table(catalog = "public",schema = "store",name = "book"
)
public static class Book {@Idprivate Long id;private String title;private String author;//Getters and setters are omitted for brevity
}

1.5.5. 实现持久化对象的 equals() hashCode()

对于同类型持久化对象,要判断是否相等,在持久化方面应该用主键信息去比较。而Java的默认情况下使用内存地址来判断,所以在使用Hibernate,用到 Set 存储关联实体时,或者有比较实体是否相等时应该考虑实现equals() hashCode()方法。

@Entity(name = "Book")
public static class Book {@Id@GeneratedValueprivate Long id;private String title;private String author;@NaturalIdprivate String isbn;//Getters and setters are omitted for brevity@Overridepublic boolean equals(Object o) {if ( this == o ) {return true;}if ( o == null || getClass() != o.getClass() ) {return false;}Book book = (Book) o;return Objects.equals( isbn, book.isbn );}@Overridepublic int hashCode() {return Objects.hash( isbn );}
}

1.5.6. 实体映射到 SQL 查询

可以使用@Subselect注解将实体映射到SQL查询。AccountSummary实体映射中@Synchronize注解的目的是指示Hibernate基础@SubselectSQL查询需要哪些数据库表

@Entity(name = "Client")
@Table(name = "client")
public static class Client {@Idprivate Long id;@Column(name = "first_name")private String firstName;@Column(name = "last_name")private String lastName;//Getters and setters omitted for brevity}@Entity(name = "Account")
@Table(name = "account")
public static class Account {@Idprivate Long id;@ManyToOneprivate Client client;private String description;//Getters and setters omitted for brevity}@Entity(name = "AccountTransaction")
@Table(name = "account_transaction")
public static class AccountTransaction {@Id@GeneratedValueprivate Long id;@ManyToOneprivate Account account;private Integer cents;private String description;//Getters and setters omitted for brevity}@Entity(name = "AccountSummary")
@Subselect("select " +"	a.id as id, " +"	concat(concat(c.first_name, ' '), c.last_name) as clientName, " +"	sum(at.cents) as balance " +"from account a " +"join client c on c.id = a.client_id " +"join account_transaction at on a.id = at.account_id " +"group by a.id, concat(concat(c.first_name, ' '), c.last_name)"
)
@Synchronize( {"client", "account", "account_transaction"} )
public static class AccountSummary {@Idprivate Long id;private String clientName;private int balance;//Getters and setters omitted for brevity}

用 entityManager 可以直接取到该实体类,但是该类只是类似数据库的视图,没有实际存储:

AccountSummary summary = entityManager.find( AccountSummary.class, 1L );

当有新的 AccountTransaction 存到数据库时,使用下面的 flush 和 refresh 方法就可以直接刷新AccountSummary里的值。

AccountTransaction transaction = new AccountTransaction();
transaction.setAccount( entityManager.getReference( Account.class, 1L ) );
transaction.setDescription( "Shopping" );
transaction.setCents( -100 * 2200 );
entityManager.persist( transaction );
entityManager.flush();
entityManager.refresh( summary );

1.5.7. 自定义代理类

当实体类被标识为 final 后,Hibernate 将不会自动生成代理类,这种情况下可以使用 @Proxy 指定代理类, 当使用session.getReference 或者 session.load 方法时,hibernate就会返回代理类:

public interface Identifiable {Long getId();void setId(Long id);
}@Entity( name = "Book" )
@Proxy(proxyClass = Identifiable.class)
public static final class Book implements Identifiable {@Idprivate Long id;private String title;private String author;@Overridepublic Long getId() {return id;}@Overridepublic void setId(Long id) {this.id = id;}//Other getters and setters omitted for brevity
}

1.5.8. 动态代理

略。

1.5.9. 自定义持久化类

该@Persister注释用于指定自定义实体或集合的持久化逻辑。

对于实体,自定义持久化类必须实现EntityPersister接口。
对于集合,自定义持久化类必须实现CollectionPersister接口。

@Entity
@Persister( impl = EntityPersister.class )
public class Author {@Idpublic Integer id;@OneToMany( mappedBy = "author" )@Persister( impl = CollectionPersister.class )public Set<Book> books = new HashSet<>();//Getters and setters omitted for brevitypublic void addBook(Book book) {this.books.add( book );book.setAuthor( this );}
}@Entity
@Persister( impl = EntityPersister.class )
public class Book {@Idpublic Integer id;private String title;@ManyToOne(fetch = FetchType.LAZY)public Author author;//Getters and setters omitted for brevity
}

1.5.10. 访问策略

略。

1.6. 标识符

标识符为实体的主键。它们用于唯一地标识每个特定实体。

Hibernate 和 JPA 都对相应的数据库列进行以下假设:

  • UNIQUE 这些值必须唯一地标识每一行。
  • NOT NULL 该值不能为空。对于复合ID,任何部分都不能为null。
  • IMMUTABLE 这些值一旦插入,就无法更改。

1.6.1. 简单标识符

简单标识符映射到单个基本属性,并使用javax.persistence.Id注释表示。

根据JPA,只能将以下类型用作标识符属性类型:

  • 任何Java基本类型
  • 任何原始包装器类型
  • java.lang.String
  • java.util.Date (TemporalType#DATE)
  • java.sql.Date
  • java.math.BigDecimal
  • java.math.BigInteger

1.6.2. 复合标识符

复合标识符对应于一个或多个持久属性。JPA规范定义的管理组合标识符的规则:

  • 复合标识符必须由“主键类”表示。主键类可以使用javax.persistence.EmbeddedId批注定义,也可以使用批注定义javax.persistence.IdClass。
  • 主键类必须是公共的,并且必须具有公共的无参数构造函数。
  • 主键类必须可序列化。
  • 主键类必须定义equals和hashCode方法,该方法与主键映射到的基础数据库类型的相等性一致。

Hibernate确实允许通过多个@Id属性在没有“主键类”的情况下定义组合标识符。

1.6.3. @EmbeddedId

简单例子

@Entity(name = "SystemUser")
public static class SystemUser {@EmbeddedIdprivate PK pk;private String name;//Getters and setters are omitted for brevity
}@Embeddable
public static class PK implements Serializable {private String subsystem;private String username;public PK(String subsystem, String username) {this.subsystem = subsystem;this.username = username;}private PK() {}@Overridepublic boolean equals(Object o) {if ( this == o ) {return true;}if ( o == null || getClass() != o.getClass() ) {return false;}PK pk = (PK) o;return Objects.equals( subsystem, pk.subsystem ) &&Objects.equals( username, pk.username );}@Overridepublic int hashCode() {return Objects.hash( subsystem, username );}
}

在使用外建作为主键的情况,还可以在主键类里使用 @ManytoOne:

@Entity(name = "SystemUser")
public static class SystemUser {@EmbeddedIdprivate PK pk;private String name;//Getters and setters are omitted for brevity
}@Entity(name = "Subsystem")
public static class Subsystem {@Idprivate String id;private String description;//Getters and setters are omitted for brevity
}@Embeddable
public static class PK implements Serializable {@ManyToOne(fetch = FetchType.LAZY)private Subsystem subsystem;private String username;public PK(Subsystem subsystem, String username) {this.subsystem = subsystem;this.username = username;}private PK() {}@Overridepublic boolean equals(Object o) {if ( this == o ) {return true;}if ( o == null || getClass() != o.getClass() ) {return false;}PK pk = (PK) o;return Objects.equals( subsystem, pk.subsystem ) &&Objects.equals( username, pk.username );}@Overridepublic int hashCode() {return Objects.hash( subsystem, username );}
}

1.6.4. @IdClass

和 @EmbededId 的区别主要是在实体类里会定义组成标识符的各个属性,而@IdClass 指向的类只是个“影子”。 一样可以加@ManytoOne。

@Entity(name = "SystemUser")
@IdClass( PK.class )
public static class SystemUser {@Idprivate String subsystem;@Idprivate String username;private String name;public PK getId() {return new PK(subsystem,username);}public void setId(PK id) {this.subsystem = id.getSubsystem();this.username = id.getUsername();}//Getters and setters are omitted for brevity
}public static class PK implements Serializable {private String subsystem;private String username;public PK(String subsystem, String username) {this.subsystem = subsystem;this.username = username;}private PK() {}//Getters and setters are omitted for brevity@Overridepublic boolean equals(Object o) {if ( this == o ) {return true;}if ( o == null || getClass() != o.getClass() ) {return false;}PK pk = (PK) o;return Objects.equals( subsystem, pk.subsystem ) &&Objects.equals( username, pk.username );}@Overridepublic int hashCode() {return Objects.hash( subsystem, username );}
}

1.6.5. 具有关联的复合标识符

Hibernate 允许使用关联关系的实体作为主键。PersonAddress实体标识符由两个@ManyToOne关联组成。

@Entity(name = "Book")
public static class Book implements Serializable {@Id@ManyToOne(fetch = FetchType.LAZY)private Author author;@Id@ManyToOne(fetch = FetchType.LAZY)private Publisher publisher;@Idprivate String title;public Book(Author author, Publisher publisher, String title) {this.author = author;this.publisher = publisher;this.title = title;}private Book() {}//Getters and setters are omitted for brevity@Overridepublic boolean equals(Object o) {if ( this == o ) {return true;}if ( o == null || getClass() != o.getClass() ) {return false;}Book book = (Book) o;return Objects.equals( author, book.author ) &&Objects.equals( publisher, book.publisher ) &&Objects.equals( title, book.title );}@Overridepublic int hashCode() {return Objects.hash( author, publisher, title );}
}

在 Hibernate 查询时使用如下语句:

Book book = entityManager.find( Book.class, new Book(author,publisher,"High-Performance Java Persistence"
) );

1.6.6. 标识符和自动生成属性

Hibernate 不允许标识符带有 @Generated, @CreationTimestamp or @ValueGenerationType 之类的自动生成注解。应该在持久化前,在代码直接访问标识符属性赋值。

1.6.7. 生成标识符的值

标识符值的生成使用 @javax.persistence.GeneratedValue 注解指示。而如何生成标识符的值则是用 GenerationType 来表示:

  • AUTO(默认)把主键生成策略交给持久化引擎。

  • IDENTITY 此种主键生成策略就是通常所说的主键自增长,数据库在插入数据时,会自动给主键赋值,比如MYSQL可以在创建表时声明"auto_increment" 来指定主键自增长。

  • SEQUENCE 在某些数据库中,不支持主键自增长,比如Oracle,其提供了一种叫做"序列(sequence)"的机制生成主键。此时,GenerationType.SEQUENCE就可以作为主键生成策略。该策略一般与另外一个注解一起使用@SequenceGenerator,@SequenceGenerator注解指定了生成主键的序列.然后JPA会根据注解内容创建一个序列(或使用一个现有的序列)。如果不指定序列,则会自动生成一个序列 hibernate_sequence。

@Entity(name = "Product")
public static class Product {@Id@GeneratedValue(strategy = GenerationType.SEQUENCE,generator = "sequence-generator")@SequenceGenerator(name = "sequence-generator",sequenceName = "product_sequence")private Long id;@Column(name = "product_name")private String name;//Getters and setters are omitted for brevity}
  • TABLE 使用一个特定的数据库表格来保存主键,持久化引擎通过关系数据库的一张特定的表格来生成主键,这种策略的好处就是不依赖于外部环境和数据库的具体实现,在不同数据库间可以很容易的进行移植,但由于其不能充分利用数据库的特性,所以不会优先使用。该策略一般与另外一个注解一起使用@TableGenerator,@TableGenerator注解指定了生成主键的表。如果不指定序列表,则会生成一张默认的序列表,表中的列名也是自动生成,数据库上会生成一张作为sequence的表。
create table hibernate_sequences (sequence_name varchar2(255 char) not null,next_val number(19,0),primary key (sequence_name)
)

1.6.8. UUID 生成器

Hibernate支持UUID标识符值生成。这是通过其org.hibernate.id.UUIDGenerator 支持的。
使用默认 UUID 生成策略:

@Entity(name = "Book")
public static class Book {@Id@GeneratedValueprivate UUID id;private String title;private String author;//Getters and setters are omitted for brevity
}

指定策略:

@Entity(name = "Book")
public static class Book {@Id@GeneratedValue( generator = "custom-uuid" )@GenericGenerator(name = "custom-uuid",strategy = "org.hibernate.id.UUIDGenerator",parameters = {@Parameter(name = "uuid_gen_strategy_class",value = "org.hibernate.id.uuid.CustomVersionOneStrategy")})private UUID id;private String title;private String author;//Getters and setters are omitted for brevity
}

1.6.9. 标识符优化器

Needed.

1.6.10. @GenericGenerator

@GenericGenerator允许集成任何Hibernate org.hibernate.id.IdentifierGenerator实现。要使用池式优化器,实体映射必须使用@GenericGenerator注释:

@Entity(name = "Product")
public static class Product {@Id@GeneratedValue(strategy = GenerationType.SEQUENCE,generator = "product_generator")@GenericGenerator(name = "product_generator",strategy = "org.hibernate.id.enhanced.SequenceStyleGenerator",parameters = {@Parameter(name = "sequence_name", value = "product_sequence"),@Parameter(name = "initial_value", value = "1"),@Parameter(name = "increment_size", value = "3"),@Parameter(name = "optimizer", value = "pooled-lo")})private Long id;@Column(name = "p_name")private String name;@Column(name = "p_number")private String number;//Getters and setters are omitted for brevity}

1.6.11. 派生标识符

如果两个实体的关系是一对一的关系,则他们的主键标识符可以一样。可以使用 @MapsId / @PrimaryKeyJoinColumn 同步两个实体的标识符:

@Entity(name = "Person")
public static class Person  {@Idprivate Long id;@NaturalIdprivate String registrationNumber;public Person() {}public Person(String registrationNumber) {this.registrationNumber = registrationNumber;}//Getters and setters are omitted for brevity
}@Entity(name = "PersonDetails")
public static class PersonDetails  {@Idprivate Long id;private String nickName;@OneToOne@MapsIdprivate Person person;//Getters and setters are omitted for brevity
}

PersonDetails实体标识符的值是从其父Person实体的标识符“派生”的。存储时直接用Person的id值:

doInJPA( this::entityManagerFactory, entityManager -> {Person person = new Person( "ABC-123" );person.setId( 1L );entityManager.persist( person );PersonDetails personDetails = new PersonDetails();personDetails.setNickName( "John Doe" );personDetails.setPerson( person );entityManager.persist( personDetails );
} );doInJPA( this::entityManagerFactory, entityManager -> {PersonDetails personDetails = entityManager.find( PersonDetails.class, 1L );assertEquals("John Doe", personDetails.getNickName());
} );

这个方式也可以用在复合标识符 @EmbeddedId 的情况。

1.6.12. @RowId

@RowId 可以注解实体类,用Hibnernate 从数据库取出持久化类后,进行crud操作时,hibernate 会使用一个rowid作为条件操作数据库。

@Entity(name = "Product")
@RowId("ROWID")
public static class Product {@Idprivate Long id;@Column(name = "`name`")private String name;@Column(name = "`number`")private String number;//Getters and setters are omitted for brevity}
Product product = entityManager.find( Product.class, 1L );product.setName( "Smart phone" );
SELECTp.id as id1_0_0_,p."name" as name2_0_0_,p."number" as number3_0_0_,p.ROWID as rowid_0_
FROMProduct p
WHEREp.id = ?-- binding parameter [1] as [BIGINT] - [1]-- extracted value ([name2_0_0_] : [VARCHAR]) - [Mobile phone]
-- extracted value ([number3_0_0_] : [VARCHAR]) - [123-456-7890]
-- extracted ROWID value: AAAwkBAAEAAACP3AAAUPDATEProduct
SET"name" = ?,"number" = ?
WHEREROWID = ?-- binding parameter [1] as [VARCHAR] - [Smart phone]
-- binding parameter [2] as [VARCHAR] - [123-456-7890]
-- binding parameter [3] as ROWID     - [AAAwkBAAEAAACP3AAA]

1.7. 关联关系

1.7.1. @ManyToOne

@ManyToOne 是最常见的关联,在关系数据库中也具有直接等效项(例如,外键),因此它在子实体和父实体之间建立了关系。这里直接用 @JoinColume 创建外键。@JoinColumn所在实体是关系拥有方。

@Entity(name = "Person")
public static class Person {@Id@GeneratedValueprivate Long id;//Getters and setters are omitted for brevity}@Entity(name = "Phone")
public static class Phone {@Id@GeneratedValueprivate Long id;@Column(name = "`number`")private String number;@ManyToOne@JoinColumn(name = "person_id",foreignKey = @ForeignKey(name = "PERSON_ID_FK"))private Person person;//Getters and setters are omitted for brevity}

1.7.2. @OneToMany

1.7.2.1. 单向

一对多关联关系。如果一方有 @OneToMany 的注解,而多的一方没有 @ManyToOne,则这样的关联关系是单向的。单向一对多关联关系,需要一张独立的表来负责维护。在删除子实体时,单向关联不是非常有效。

@Entity(name = "Person")
public static class Person {@Id@GeneratedValueprivate Long id;@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)private List<Phone> phones = new ArrayList<>();//Getters and setters are omitted for brevity}@Entity(name = "Phone")
public static class Phone {@Id@GeneratedValueprivate Long id;@Column(name = "`number`")private String number;//Getters and setters are omitted for brevity}

以上的实体类结构所对应的数据库结构:

CREATE TABLE Person (id BIGINT NOT NULL ,PRIMARY KEY ( id )
)CREATE TABLE Person_Phone (Person_id BIGINT NOT NULL ,phones_id BIGINT NOT NULL
)CREATE TABLE Phone (id BIGINT NOT NULL ,number VARCHAR(255) ,PRIMARY KEY ( id )
)ALTER TABLE Person_Phone
ADD CONSTRAINT UK_9uhc5itwc9h5gcng944pcaslf
UNIQUE (phones_id)ALTER TABLE Person_Phone
ADD CONSTRAINT FKr38us2n8g5p9rj0b494sd3391
FOREIGN KEY (phones_id) REFERENCES PhoneALTER TABLE Person_Phone
ADD CONSTRAINT FK2ex4e4p7w1cj310kg2woisjl2
FOREIGN KEY (Person_id) REFERENCES Person

1.7.2.2. 双向

双向一对多关联关系需要在多方加上一方的引用,并且加上 @ManyToOne。这样双方都能取到对方的数据,而为了不再生成中间表,需要把创建外键的任务交给多的一方,在 @OneToMany 里加上 mappedBy 属性,值为一方在多方里的引用变量名称,在这个例子中就是 “person” 。

orphanRemoval的作用是某个 phone 不在被父实体引用后,是否将其在Phone表中删除,如果标为false,则只是将Phone表中相应记录的外键值设置为 Null。

@Entity(name = "Person")
public static class Person {@Id@GeneratedValueprivate Long id;@OneToMany(mappedBy = "person", cascade = CascadeType.ALL, orphanRemoval = true)private List<Phone> phones = new ArrayList<>();//Getters and setters are omitted for brevitypublic void addPhone(Phone phone) {phones.add( phone );phone.setPerson( this );}public void removePhone(Phone phone) {phones.remove( phone );phone.setPerson( null );}
}@Entity(name = "Phone")
public static class Phone {@Id@GeneratedValueprivate Long id;@NaturalId@Column(name = "`number`", unique = true)private String number;@ManyToOneprivate Person person;//Getters and setters are omitted for brevity@Overridepublic boolean equals(Object o) {if ( this == o ) {return true;}if ( o == null || getClass() != o.getClass() ) {return false;}Phone phone = (Phone) o;return Objects.equals( number, phone.number );}@Overridepublic int hashCode() {return Objects.hash( number );}
}

双向一对多的数据库结构:

CREATE TABLE Person (id BIGINT NOT NULL ,PRIMARY KEY ( id )
)CREATE TABLE Phone (id BIGINT NOT NULL ,number VARCHAR(255) ,person_id BIGINT ,PRIMARY KEY ( id )
)ALTER TABLE Phone
ADD CONSTRAINT UK_l329ab0g4c1t78onljnxmbnp6
UNIQUE (number)ALTER TABLE Phone
ADD CONSTRAINT FKmw13yfsjypiiq0i1osdkaeqpg
FOREIGN KEY (person_id) REFERENCES Person

1.7.3. @OneToOne

1.7.3.1. 单向

单向一对一关系,只要在其中一方的实体内加上 @OneToOne 负责关联关系和 @JoinColume 来创建外键。

@Entity(name = "Phone")
public static class Phone {@Id@GeneratedValueprivate Long id;@Column(name = "`number`")private String number;@OneToOne@JoinColumn(name = "details_id")private PhoneDetails details;//Getters and setters are omitted for brevity}@Entity(name = "PhoneDetails")
public static class PhoneDetails {@Id@GeneratedValueprivate Long id;private String provider;private String technology;//Getters and setters are omitted for brevity}

数据库结构:

CREATE TABLE Phone (id BIGINT NOT NULL ,number VARCHAR(255) ,details_id BIGINT ,PRIMARY KEY ( id )
)CREATE TABLE PhoneDetails (id BIGINT NOT NULL ,provider VARCHAR(255) ,technology VARCHAR(255) ,PRIMARY KEY ( id )
)ALTER TABLE Phone
ADD CONSTRAINT FKnoj7cj83ppfqbnvqqa5kolub7
FOREIGN KEY (details_id) REFERENCES PhoneDetails

1.7.3.2. 双向

双向一对一关系需要在两方都加上对方的引用并加上 @OneToOne 注解。

在这里可以在其中一方加上 mappedBy 属性避免生成两个外键。一般来说,应该在父实体类上加上 mappedBy,让子实体类的生命周期跟随父实体类,生成外键维护关系。例如 Phone 和 PhoneDetails,Phone应为父实体类,因为现有 Phone 才有 PhoneDetails。删除Phone的记录时,级联删除 PhoneDetails(CascadeType.ALL)。

@Entity(name = "Phone")
public static class Phone {@Id@GeneratedValueprivate Long id;@Column(name = "`number`")private String number;@OneToOne(mappedBy = "phone",cascade = CascadeType.ALL,orphanRemoval = true,fetch = FetchType.LAZY)private PhoneDetails details;//Getters and setters are omitted for brevitypublic void addDetails(PhoneDetails details) {details.setPhone( this );this.details = details;}public void removeDetails() {if ( details != null ) {details.setPhone( null );this.details = null;}}
}@Entity(name = "PhoneDetails")
public static class PhoneDetails {@Id@GeneratedValueprivate Long id;private String provider;private String technology;@OneToOne(fetch = FetchType.LAZY)@JoinColumn(name = "phone_id")private Phone phone;//Getters and setters are omitted for brevity}

1.7.4. @ManyToMany

1.7.4.1. 单向

在单向多对多的关系,级联操作不要包括删除,因为 Address 有可能被两个 Person 引用。

和 @OneToMany 一样,在单向关系中,把多方的某些元素删除时,Hibernate 需要把中间表中多方的关联元素删除,在把没指定删除的元素在加回去。

@Entity(name = "Person")
public static class Person {@Id@GeneratedValueprivate Long id;@ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE})private List<Address> addresses = new ArrayList<>();//Getters and setters are omitted for brevity}@Entity(name = "Address")
public static class Address {@Id@GeneratedValueprivate Long id;private String street;@Column(name = "`number`")private String number;//Getters and setters are omitted for brevity}

数据库:

CREATE TABLE Address (id BIGINT NOT NULL ,number VARCHAR(255) ,street VARCHAR(255) ,PRIMARY KEY ( id )
)CREATE TABLE Person (id BIGINT NOT NULL ,PRIMARY KEY ( id )
)CREATE TABLE Person_Address (Person_id BIGINT NOT NULL ,addresses_id BIGINT NOT NULL
)

1.7.4.2. 无链接实体的双向

双向ManyToMany时依赖的依然是mappedBy。在两个实体中都需要注解@ManyToMany,但是因为是多对多,需要依赖虚拟的中间表,在下面的例子中在 Address 中标注 mappedBy,让Person管理这张表(Person_Address),否则会生成另一张中间表(Address_Person)。在Person类的addAddress() 和 removeAddress()中会有对Address的操作。

但是这样的双向关系并没有像双向一对多那样对删除操作有优化作用,因为外键不受控制,需要经过删除所有记录,在把没指定删除的加回来。

@Entity(name = "Person")
public static class Person {@Id@GeneratedValueprivate Long id;@NaturalIdprivate String registrationNumber;@ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE})private List<Address> addresses = new ArrayList<>();//Getters and setters are omitted for brevitypublic void addAddress(Address address) {addresses.add( address );address.getOwners().add( this );}public void removeAddress(Address address) {addresses.remove( address );address.getOwners().remove( this );}@Overridepublic boolean equals(Object o) {if ( this == o ) {return true;}if ( o == null || getClass() != o.getClass() ) {return false;}Person person = (Person) o;return Objects.equals( registrationNumber, person.registrationNumber );}@Overridepublic int hashCode() {return Objects.hash( registrationNumber );}
}@Entity(name = "Address")
public static class Address {@Id@GeneratedValueprivate Long id;private String street;@Column(name = "`number`")private String number;private String postalCode;@ManyToMany(mappedBy = "addresses")private List<Person> owners = new ArrayList<>();//Getters and setters are omitted for brevity@Overridepublic boolean equals(Object o) {if ( this == o ) {return true;}if ( o == null || getClass() != o.getClass() ) {return false;}Address address = (Address) o;return Objects.equals( street, address.street ) &&Objects.equals( number, address.number ) &&Objects.equals( postalCode, address.postalCode );}@Overridepublic int hashCode() {return Objects.hash( street, number, postalCode );}
}

数据库结构:

CREATE TABLE Address (id BIGINT NOT NULL ,number VARCHAR(255) ,postalCode VARCHAR(255) ,street VARCHAR(255) ,PRIMARY KEY ( id )
)CREATE TABLE Person (id BIGINT NOT NULL ,registrationNumber VARCHAR(255) ,PRIMARY KEY ( id )
)CREATE TABLE Person_Address (owners_id BIGINT NOT NULL ,addresses_id BIGINT NOT NULL
)

1.7.4.3. 有链接实体的双向

为了多对多关系也能对删除操作有优化,必须直接公开链接表并将 @ManyToMany 关联分为两个双向 @OneToMany 关系。

@Entity(name = "Person")
public static class Person implements Serializable {@Id@GeneratedValueprivate Long id;@NaturalIdprivate String registrationNumber;@OneToMany(mappedBy = "person",cascade = CascadeType.ALL,orphanRemoval = true)private List<PersonAddress> addresses = new ArrayList<>();//Getters and setters are omitted for brevitypublic void addAddress(Address address) {PersonAddress personAddress = new PersonAddress( this, address );addresses.add( personAddress );address.getOwners().add( personAddress );}public void removeAddress(Address address) {PersonAddress personAddress = new PersonAddress( this, address );address.getOwners().remove( personAddress );addresses.remove( personAddress );personAddress.setPerson( null );personAddress.setAddress( null );}@Overridepublic boolean equals(Object o) {if ( this == o ) {return true;}if ( o == null || getClass() != o.getClass() ) {return false;}Person person = (Person) o;return Objects.equals( registrationNumber, person.registrationNumber );}@Overridepublic int hashCode() {return Objects.hash( registrationNumber );}
}@Entity(name = "PersonAddress")
public static class PersonAddress implements Serializable {@Id@ManyToOneprivate Person person;@Id@ManyToOneprivate Address address;//Getters and setters are omitted for brevity@Overridepublic boolean equals(Object o) {if ( this == o ) {return true;}if ( o == null || getClass() != o.getClass() ) {return false;}PersonAddress that = (PersonAddress) o;return Objects.equals( person, that.person ) &&Objects.equals( address, that.address );}@Overridepublic int hashCode() {return Objects.hash( person, address );}
}@Entity(name = "Address")
public static class Address implements Serializable {@Id@GeneratedValueprivate Long id;private String street;@Column(name = "`number`")private String number;private String postalCode;@OneToMany(mappedBy = "address",cascade = CascadeType.ALL,orphanRemoval = true)private List<PersonAddress> owners = new ArrayList<>();//Getters and setters are omitted for brevity@Overridepublic boolean equals(Object o) {if ( this == o ) {return true;}if ( o == null || getClass() != o.getClass() ) {return false;}Address address = (Address) o;return Objects.equals( street, address.street ) &&Objects.equals( number, address.number ) &&Objects.equals( postalCode, address.postalCode );}@Overridepublic int hashCode() {return Objects.hash( street, number, postalCode );}
}

这种方法在删除时只会触发一句sql:

person1.removeAddress( address1 );
DELETE FROM PersonAddress WHERE person_id = 1 AND address_id = 3

1.8. @NaturalId

1.8.1. 映射

NaturalId 代表领域模型唯一标识符,比如身份证,电话号码,书籍的ISBN等等。一个实体类可以有多个
@NaturalId。

单个属性的 NaturalId:

@Entity(name = "Book")
public static class Book {@Idprivate Long id;private String title;private String author;@NaturalIdprivate String isbn;//Getters and setters are omitted for brevity
}

带嵌入属性的 NaturalId:

@Entity(name = "Book")
public static class Book {@Idprivate Long id;private String title;private String author;@NaturalId@Embeddedprivate Isbn isbn;//Getters and setters are omitted for brevity
}
@Embeddable
public static class Isbn implements Serializable {private String isbn10;private String isbn13;//Getters and setters are omitted for brevity@Overridepublic boolean equals(Object o) {if ( this == o ) {return true;}if ( o == null || getClass() != o.getClass() ) {return false;}Isbn isbn = (Isbn) o;return Objects.equals( isbn10, isbn.isbn10 ) &&Objects.equals( isbn13, isbn.isbn13 );}@Overridepublic int hashCode() {return Objects.hash( isbn10, isbn13 );}
}

多个属性的 NaturalId:

@Entity(name = "Book")
public static class Book {@Idprivate Long id;private String title;private String author;@NaturalIdprivate String productNumber;@NaturalId@ManyToOne(fetch = FetchType.LAZY)private Publisher publisher;//Getters and setters are omitted for brevity
}@Entity(name = "Publisher")
public static class Publisher implements Serializable {@Idprivate Long id;private String name;//Getters and setters are omitted for brevity@Overridepublic boolean equals(Object o) {if ( this == o ) {return true;}if ( o == null || getClass() != o.getClass() ) {return false;}Publisher publisher = (Publisher) o;return Objects.equals( id, publisher.id ) &&Objects.equals( name, publisher.name );}@Overridepublic int hashCode() {return Objects.hash( id, name );}
}

1.8.2. 使用 @NaturalId 加载

Book book = entityManager.unwrap(Session.class).byNaturalId( Book.class ).using( "isbn", "978-9730228236" ).load();Book book = entityManager.unwrap(Session.class).byNaturalId( Book.class ).using("isbn",new Isbn("973022823X","978-9730228236") ).load();Book book = entityManager.unwrap(Session.class).byNaturalId( Book.class ).using("productNumber", "973022823X").using("publisher", publisher).load();

因为前两个例子中都只有一个NaturalId,可以用简单的方式加载:

Book book = entityManager.unwrap(Session.class).bySimpleNaturalId( Book.class ).load( "978-9730228236" );Book book = entityManager.unwrap(Session.class).bySimpleNaturalId( Book.class ).load(new Isbn("973022823X","978-9730228236"));

NaturalIdLoadAccess提供2种不同的方法来获取实体:

  • load() 获取对实体的引用,确保实体状态已初始化。
  • getReference() 获取对该实体的引用。状态可以初始化也可以不初始化。如果该实体已经与当前正在运行的Session关联,则返回该引用(已加载或未加载)。如果该实体未在当前会话中加载并且该实体支持代理生成,则会生成并返回未初>始化的代理,否则从数据库加载该实体并返回。

1.8.3. 可变性和缓存

默认情况下,NaturalId 是不可以更改的。如果要改变这一设定就要使用 @NaturalId(mutable = true)。在一个 session 中 Hibernate 维护着一个从 naturalId 到主键的映射,如果一个natural改变了,但是还没刷新上下文,这个映射关系可能会不准确:

Author author = entityManager.unwrap(Session.class).bySimpleNaturalId( Author.class ).load( "john@acme.com" );author.setEmail( "john.doe@acme.com" );assertNull(entityManager.unwrap(Session.class).bySimpleNaturalId( Author.class ).load( "john.doe@acme.com" )
);

为了在这种情况下能获取到正确的实体,应该加上这个方法 setSynchronizationEnabled(true)

assertSame( author,entityManager.unwrap(Session.class).bySimpleNaturalId( Author.class ).setSynchronizationEnabled( true ).load( "john.doe@acme.com" )
);

1.9. 继承

尽管关系数据库系统不提供对继承的支持,但是Hibernate提供了几种策略来将这种面向对象的特征利用到域模型实体上:

  • Mapped Super class
    继承仅在域模型中实现,而没有在数据库模式中反映出来。只有子类有数据库表。

  • Single table
    域模型类层次结构被具体化为一个表,该表包含属于不同类类型的实体。

  • Joined table
    基类和所有子类都有自己的数据库表,并且获取子类实体也需要与父表进行联接。

  • Table per calss
    每个子类都有自己的表,其中包含子类和基类属性。

1.9.1. Mapped Super class

使用时MappedSuperclass,只有子类有数据库表,继承仅在域模型中可见,并且每个子类的数据库表都包含基类和子类属性。

@MappedSuperclass
public static class Account {@Idprivate Long id;private String owner;private BigDecimal balance;private BigDecimal interestRate;//Getters and setters are omitted for brevity
}@Entity(name = "DebitAccount")
public static class DebitAccount extends Account {private BigDecimal overdraftFee;//Getters and setters are omitted for brevity
}@Entity(name = "CreditAccount")
public static class CreditAccount extends Account {private BigDecimal creditLimit;//Getters and setters are omitted for brevity
}

数据库:

CREATE TABLE DebitAccount (id BIGINT NOT NULL ,balance NUMERIC(19, 2) ,interestRate NUMERIC(19, 2) ,owner VARCHAR(255) ,overdraftFee NUMERIC(19, 2) ,PRIMARY KEY ( id )
)CREATE TABLE CreditAccount (id BIGINT NOT NULL ,balance NUMERIC(19, 2) ,interestRate NUMERIC(19, 2) ,owner VARCHAR(255) ,creditLimit NUMERIC(19, 2) ,PRIMARY KEY ( id )
)

1.9.2. Single table

单表继承策略将所有子类仅映射到一个数据库表。每个子类声明其自己的持久属性。当省略显式继承策略(例如@Inheritance)时,JPA 将选择 SINGLE_TABLE 作默认策略。

@Entity(name = "Account")
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
public static class Account {@Idprivate Long id;private String owner;private BigDecimal balance;private BigDecimal interestRate;//Getters and setters are omitted for brevity
}@Entity(name = "DebitAccount")
public static class DebitAccount extends Account {private BigDecimal overdraftFee;//Getters and setters are omitted for brevity
}@Entity(name = "CreditAccount")
public static class CreditAccount extends Account {private BigDecimal creditLimit;//Getters and setters are omitted for brevity
}

数据库:

CREATE TABLE Account (DTYPE VARCHAR(31) NOT NULL ,id BIGINT NOT NULL ,balance NUMERIC(19, 2) ,interestRate NUMERIC(19, 2) ,owner VARCHAR(255) ,overdraftFee NUMERIC(19, 2) ,creditLimit NUMERIC(19, 2) ,PRIMARY KEY ( id )
)

1.9.3. Joined Table

超类和子类有各自的表。每个子类都拥有一个指向超类的外键,并且这个外键是子类的主键。可以用 @PrimaryKeyJoinColumn 来指定外键列的名称。

@Entity(name = "Account")
@Inheritance(strategy = InheritanceType.JOINED)
public static class Account {@Idprivate Long id;private String owner;private BigDecimal balance;private BigDecimal interestRate;//Getters and setters are omitted for brevity
}@Entity(name = "DebitAccount")
public static class DebitAccount extends Account {private BigDecimal overdraftFee;//Getters and setters are omitted for brevity
}@Entity(name = "CreditAccount")
public static class CreditAccount extends Account {private BigDecimal creditLimit;//Getters and setters are omitted for brevity
}

数据库:

CREATE TABLE Account (id BIGINT NOT NULL ,balance NUMERIC(19, 2) ,interestRate NUMERIC(19, 2) ,owner VARCHAR(255) ,PRIMARY KEY ( id )
)CREATE TABLE CreditAccount (creditLimit NUMERIC(19, 2) ,id BIGINT NOT NULL ,PRIMARY KEY ( id )
)CREATE TABLE DebitAccount (overdraftFee NUMERIC(19, 2) ,id BIGINT NOT NULL ,PRIMARY KEY ( id )
)ALTER TABLE CreditAccount
ADD CONSTRAINT FKihw8h3j1k0w31cnyu7jcl7n7n
FOREIGN KEY (id) REFERENCES AccountALTER TABLE DebitAccount
ADD CONSTRAINT FKia914478noepymc468kiaivqm
FOREIGN KEY (id) REFERENCES Account

1.9.4. Table Per Class

子类和超类都会生成表。子表显示拥有超类的属性。

略。

2. 引导程序

2.1. 构建 ServiceRegistry

有两种 ServiceRegistry, BootstrapServiceRegistry 和 StandardServiceRegistry。他们主要管理Hibernate 在引导和运行时所需要的依赖服务:

  • ClassLoaderService,它控制 Hibernate 与 ClassLoader 的交互方式。
  • IntegratorService,控制 Integrator 实例的管理和发现。
  • StrategySelector,控制了Hibernate 如何解决各种 strategy 的实现。

一般不需要定制化这些 service,直接使用 Hibernate 默认配置。

BootstrapServiceRegistry bootstrapRegistry = new BootstrapServiceRegistryBuilder().build();
ServiceRegistry standardRegistry = new StandardServiceRegistryBuilder().build();

2.2. 监听

略。

2.3. 配置元数据

本机自举的第二步是构建一个org.hibernate.boot.Metadata对象,用来注册实体模型和映射关系。

ServiceRegistry standardRegistry =new StandardServiceRegistryBuilder().build();MetadataSources sources = new MetadataSources( standardRegistry ).addAnnotatedClass( MyEntity.class ).addAnnotatedClassName( "org.hibernate.example.Customer" ).addResource( "org/hibernate/example/Order.hbm.xml" ).addResource( "org/hibernate/example/Product.orm.xml" );

如果需要更多的控制映射过程,使用 getMetadataBuilder():

Metadata metadata = new MetadataSources( standardRegistry ).addAnnotatedClass( MyEntity.class ).addAnnotatedClassName( "org.hibernate.example.Customer" ).addResource( "org/hibernate/example/Order.hbm.xml" ).addResource( "org/hibernate/example/Product.orm.xml" ).getMetadataBuilder().applyImplicitNamingStrategy( ImplicitNamingStrategyJpaCompliantImpl.INSTANCE ).build();

2.4. SessionFactroy

第三步构建 SessionFactory:

StandardServiceRegistry standardRegistry = new StandardServiceRegistryBuilder().configure( "org/hibernate/example/hibernate.cfg.xml" ).build();Metadata metadata = new MetadataSources( standardRegistry ).addAnnotatedClass( MyEntity.class ).addAnnotatedClassName( "org.hibernate.example.Customer" ).addResource( "org/hibernate/example/Order.hbm.xml" ).addResource( "org/hibernate/example/Product.orm.xml" ).getMetadataBuilder().applyImplicitNamingStrategy( ImplicitNamingStrategyJpaCompliantImpl.INSTANCE ).build();SessionFactory sessionFactory = metadata.getSessionFactoryBuilder().applyBeanManager( getBeanManager() ).build();

3. Schema Generation

Hibernate允许您从实体映射生成数据库。传统上,从实体映射生成架构的过程称为HBM2DDL。
对于下列的实体:

@Entity(name = "Customer")
public class Customer {@Idprivate Integer id;private String name;@Basic( fetch = FetchType.LAZY )private UUID accountsPayableXrefId;@Lob@Basic( fetch = FetchType.LAZY )@LazyGroup( "lobs" )private Blob image;//Getters and setters are omitted for brevity
}@Entity(name = "Person")
public static class Person {@Idprivate Long id;private String name;@OneToMany(mappedBy = "author")private List<Book> books = new ArrayList<>();//Getters and setters are omitted for brevity
}@Entity(name = "Book")
public static class Book {@Idprivate Long id;private String title;@NaturalIdprivate String isbn;@ManyToOneprivate Person author;//Getters and setters are omitted for brevity
}

如果hibernate.hbm2ddl.auto配置设置为create,则Hibernate将生成以下数据库架构:

create table Customer (id integer not null,accountsPayableXrefId binary,image blob,name varchar(255),primary key (id)
)create table Book (id bigint not null,isbn varchar(255),title varchar(255),author_id bigint,primary key (id)
)create table Person (id bigint not null,name varchar(255),primary key (id)
)alter table Bookadd constraint UK_u31e1frmjp9mxf8k8tmp990i unique (isbn)alter table Bookadd constraint FKrxrgiajod1le3gii8whx2doieforeign key (author_id)references Person

3.1. 导入脚本文件

例如,考虑以下schema-generation.sql导入文件:

create sequence book_sequence start with 1 increment by 1

如果我们将Hibernate配置为导入上面的脚本:

<property name="hibernate.hbm2ddl.import_files" value="schema-generation.sql" />

模式自动生成后,Hibernate将执行脚本文件。

3.2. 数据库层的检查

Hibernate提供了@Check注释,以便您可以指定一个任意的 SQL CHECK 约束,该约束可以如下定义:

@Entity(name = "Book")
@Check( constraints = "CASE WHEN isbn IS NOT NULL THEN LENGTH(isbn) = 13 ELSE true END")
public static class Book {@Idprivate Long id;private String title;@NaturalIdprivate String isbn;private Double price;//Getters and setters omitted for brevity
}

如果现在持久化一个 Book 实例,而 isbn 没有达到 13 位,就会抛错。

3.3. 数据库列的默认值

使用Hibernate,您可以使用@ColumnDefault注释为给定的数据库列指定默认值。

@DynamicInsert属性:设置为 true,设置为true,表示 insert 对象的时候,生成动态的 insert 语句,如果这个字段的值是 null 就不会加入到 insert 语句当中.默认 false。数据库插入日期或时间戳字段时,在对象字段为空的情况下,表字段能自动填写当前的 sysdate。

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

@Entity(name = "Person")
@DynamicInsert
public static class Person {@Idprivate Long id;@ColumnDefault("'N/A'")private String name;@ColumnDefault("-1")private Long clientId;//Getter and setters omitted for brevity}

3.4. 唯一约束

@UniqueConstraint:

@Entity
@Table(name = "book",uniqueConstraints =  @UniqueConstraint(name = "uk_book_title_author",columnNames = {"title","author_id"})
)
public static class Book {@Id@GeneratedValueprivate Long id;private String title;@ManyToOne(fetch = FetchType.LAZY)@JoinColumn(name = "author_id",foreignKey = @ForeignKey(name = "fk_book_author_id"))private Author author;//Getter and setters omitted for brevity
}@Entity
@Table(name = "author")
public static class Author {@Id@GeneratedValueprivate Long id;@Column(name = "first_name")private String firstName;@Column(name = "last_name")private String lastName;//Getter and setters omitted for brevity
}

3.5. 索引

@Index注释用于创建数据库索引:

@Entity
@Table(name = "author",indexes =  @Index(name = "idx_author_first_last_name",columnList = "first_name, last_name",unique = false)
)
public static class Author {@Id@GeneratedValueprivate Long id;@Column(name = "first_name")private String firstName;@Column(name = "last_name")private String lastName;//Getter and setters omitted for brevity
}
create table author (id bigint not null,first_name varchar(255),last_name varchar(255),primary key (id)
)create index idx_author_first_last_name on author (first_name, last_name)
查看全文
如若内容造成侵权/违法违规/事实不符,请联系编程学习网邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!

相关文章

  1. jpa 实体注解

    导入的包:import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.Table;1. @Entity被实体标注的实体类将会被JPA管…...

    2024/4/24 23:25:43
  2. 资源文件读取

    Android资源主要包括文本字符串(strings)、颜色(colors)、数组(arrays)、动画(anim)、布局(layout)、图像和图标(drawable)、音频视频(media)和其他应用程序使用的组件。Resources\InputStream\Scanner等对象,示例如下: Resources rs = getResources();InputStream fs = rs.…...

    2024/5/9 14:39:57
  3. python3.4.4实现网页爬虫基础之网页下载器三种方法

    这是是慕课网《Python开发简单爬虫》中网页下载器的三种实现方法,课程用的是python2.7,这里用最新的3.4.4实现出来,分享给新人:import urllib.request from http.cookiejar import CookieJarurl = http://www.baidu.comprint(第一种方法) res1 = urllib.request.urlopen(ur…...

    2024/4/24 23:25:44
  4. java中static关键字的使用说明

    1、static关键字的用途 在《Java编程思想》P86页有这样一段话:“static方法就是没有this的方法。在static方法内部不能调用非静态方法,反过来是可以的。而且可以在没有创建任何对象的前提下,仅仅通过类本身来调用static方法。这实际上正是static方法的主要用途。”这段话虽…...

    2024/4/24 23:25:40
  5. Windows平台软件推荐:神器小工具(骨灰级)

    底层工具“If you know how to use Process Monitor competently, people of both sexes will immediately find you more attractive.” – Scott HanselmanUltimate Boot CD 和 Ultimate Boot CD for Windows – 这些光盘是程序员修电脑时的必备工具,毕竟你可没那么多钱给给…...

    2024/5/3 15:26:05
  6. 使用electron和nwjs开发跨平台应用

    web前端语言的发展有目共睹, 从原来的pc web, 到后来的mobile SAP, 再到 nodejs,全站工程师应运而生. js快速而且稳健的发展让人不得不重视, 相应的前端开发人员的地位也越来越高, 越来越多的人愿意投入到前端的阵营里去. 而桌面app向来是web前端开发开发人员下意识的避开方向.…...

    2024/4/24 22:32:27
  7. TypedValue.applyDimension()返回值

    TypedValue.applyDimension()该函数多用于兼容不同手机的尺寸大小。 float radius=TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 20, getResources().getDisplayMetrics());radius在1440x2400的手机上返回80,在1080x1800手机上返回60; getResources().getDispla…...

    2024/4/24 23:25:37
  8. static修饰符的作用及应用

    static 修饰符数据共享成员变量(实例变量)和静态变量(类变量)的区别 两个变量的生命周期不同 成员变量随对象的创建而存在,随对象被回收而释放 静态变量随类的加载而存在,随类的消失而消失 调用方式不同 成员变量只能被对象调用 静态变量还可以被类名调用 数据存储位…...

    2024/4/26 23:43:00
  9. IDM下载器最新稳定版本6.33.2

    IDM这款下载神器,想必大家一定陌生了。小编也曾分享过好多版本的IDM,但是IDM的更新实在是快的惊人,不久之前已经更新到了6.33 Build2。于是乐平君赶紧找了一下资源并亲自测试了一波。就乐平君自己的使用感受而言,这个版本较以前的老版本更稳定一些,更强大一些!例如之前B站…...

    2024/4/24 23:25:35
  10. SpringBoot 入门(三)——数据持久化

    后端一定得与数据打交道,所以一定会做数据持久化,在 SpringBoot 中提供的 JPA 的 Api,可以很方便的实现数据的 CRUD。一 添加依赖在数据库部分,我使用的是 MySQL,所以在 pom 文件中需要添加如下依赖:<!-- MySQL 连接依赖 --><dependency><groupId>mysq…...

    2024/4/14 21:11:57
  11. linux暴力破解工具

    对于 Linux 操作系统来说,一般通过 VNC、Teamviewer 和 SSH 等工具来进行远程管理,SSH 是 Secure Shell 的缩写,由 IETF 的网络小组(Network Working Group)所制定;SSH 为建立在应用层基础上的安全协议。SSH 是目前较可靠,专为远程登录会话和其他网络服务提供安全性的协…...

    2024/4/19 22:42:29
  12. SQL Server 2008内存及I/O性能监控

    本文由http://blog.csdn.net/yongsheng0550转自http://tech.it168.com/a2011/0221/1158/000001158998_all.shtml内存相关概念以下均是针对Window 32位系统环境下,64位的不在下面描述情况下。用户模式和内核模式(user mode& kernel mode)为了防止用户程序访问并篡改操作系统…...

    2024/5/3 15:50:35
  13. JPA进阶(二)

    1. 单向一对多 单向的一对多关联映射关系主要是通过外键来关联的。一对多的关联映射是在表示“多”的一方的数据表中增加一个外键,并由“一”的一方指向“多”的一方。 单向一对多关联的持久化类里需要包含一个集合属性,在“一”的一方访问“多”的一方时,“多”的一方将以集…...

    2024/4/14 21:11:54
  14. CSDN博客积分规则

    博客积分是CSDN对用户努力的认可和奖励,也是衡量博客水平的重要标准。博客等级也将由博客积分唯一决定。积分规则具体如下:1、每发布一篇原创或者翻译文章:可获得10分;2、每发布一篇转载文章:可获得2分;3、博主的文章每被评论一次:可获得1分;4、每发表一次评论:可获得…...

    2024/4/17 9:23:56
  15. Java技术——Java中的static关键字解析

    0. 前言 static是Java中的重要的一个点。也是面试的时候经常被问到的点,如果理解不够很容易给面试官语言基础不扎实的印象。本文从static方法、static内部类、static变量、以及static代码块四个角度分别解析static关键字。转载请注明出处为SEU_Calvin的博客。 1. static方法…...

    2024/5/3 13:07:44
  16. Electron中实现通过webview实现内嵌网页并嵌入css样式和js脚本等

    场景用HTML和CSS和JS构建跨平台桌面应用程序的开源库Electron的介绍以及搭建HelloWorld:https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/106413828Electron怎样进行渲染进程调试和使用浏览器和VSCode进行调试:https://blog.csdn.net/BADAO_LIUMANG_QIZHI/articl…...

    2024/5/3 20:05:07
  17. CSDN上传自己的资源赚积分教程

    1、登录你的CSDN2、点击下载3、点击上传资源赚积分4、按照步骤进行填写资源文件5、上传完成6、等待审核...

    2024/4/14 21:11:50
  18. static函数与普通函数

    原文:http://blog.163.com/sunshine_linting/blog/static/44893323201191294825184/全局变量(外部变量)的说明之前再冠以static 就构成了静态的全局变量。全局变量本身就是静态存储方式, 静态全局变量当然也 是静态存储方式。这两者在存储方式上并无不同。这两者的区别虽在于…...

    2024/5/3 21:36:52
  19. 【DB2】Sql优化与锁

    本次XX项目性能测试,80%性能的提升在于Sql和索引的修改。总结有以下几点: 1) 不高效的sql(不合理的sql) 2) 不合理的索引(如何建立合理的索引) 3) 避免死锁和大量锁等待 下面针对这3个方面总结下要点。 1.编写高效的Sql注意要点 1.1 表连接 表连接有两个要点: 1) 表…...

    2024/4/24 23:25:36
  20. JPA

    单向一对多 单向一对多用得比较少(性能差),但是依然要求必须掌握,因为我们在使用双向一对多还会用到单向一对多的相应配置。 映射配置 @Entity public class Product {@Id@GeneratedValueprivate Long id;private String name; @Entity public class ProductDir {@Id@Genera…...

    2024/4/24 23:25:35

最新文章

  1. 【基于 PyTorch 的 Python深度学习】5 机器学习基础(2)

    前言 文章性质&#xff1a;学习笔记 &#x1f4d6; 学习资料&#xff1a;吴茂贵《 Python 深度学习基于 PyTorch ( 第 2 版 ) 》【ISBN】978-7-111-71880-2 主要内容&#xff1a;根据学习资料撰写的学习笔记&#xff0c;该篇主要介绍了如何选择合适的激活函数、损失函数和优化器…...

    2024/5/9 15:15:50
  2. 梯度消失和梯度爆炸的一些处理方法

    在这里是记录一下梯度消失或梯度爆炸的一些处理技巧。全当学习总结了如有错误还请留言&#xff0c;在此感激不尽。 权重和梯度的更新公式如下&#xff1a; w w − η ⋅ ∇ w w w - \eta \cdot \nabla w ww−η⋅∇w 个人通俗的理解梯度消失就是网络模型在反向求导的时候出…...

    2024/5/7 10:36:02
  3. 前端 js 经典:字符编码详解

    前言&#xff1a;计算机只能识别二进制&#xff0c;开发语言中数据类型还有数字&#xff0c;字母&#xff0c;中文&#xff0c;特殊符号等&#xff0c;都需要转化成二进制编码才能让技术机识别。 一. 编码方式 ACSLL、Unicode、utf-8、URL 编码、base64 等。 1. ACSLL 对英语…...

    2024/5/9 11:41:58
  4. 蓝桥杯第十五届抱佛脚(十)贪心算法

    蓝桥杯第十五届抱佛脚&#xff08;十&#xff09;贪心算法 贪心算法基本概念 贪心算法是一种在算法设计中常用的方法&#xff0c;它在每一步选择中都采取在当前状态下最好或最优&#xff08;即最有利&#xff09;的选择&#xff0c;从而希望导致结果是最好或最优的算法。 贪…...

    2024/5/5 8:38:28
  5. 【外汇早评】美通胀数据走低,美元调整

    原标题:【外汇早评】美通胀数据走低,美元调整昨日美国方面公布了新一期的核心PCE物价指数数据,同比增长1.6%,低于前值和预期值的1.7%,距离美联储的通胀目标2%继续走低,通胀压力较低,且此前美国一季度GDP初值中的消费部分下滑明显,因此市场对美联储后续更可能降息的政策…...

    2024/5/8 6:01:22
  6. 【原油贵金属周评】原油多头拥挤,价格调整

    原标题:【原油贵金属周评】原油多头拥挤,价格调整本周国际劳动节,我们喜迎四天假期,但是整个金融市场确实流动性充沛,大事频发,各个商品波动剧烈。美国方面,在本周四凌晨公布5月份的利率决议和新闻发布会,维持联邦基金利率在2.25%-2.50%不变,符合市场预期。同时美联储…...

    2024/5/9 15:10:32
  7. 【外汇周评】靓丽非农不及疲软通胀影响

    原标题:【外汇周评】靓丽非农不及疲软通胀影响在刚结束的周五,美国方面公布了新一期的非农就业数据,大幅好于前值和预期,新增就业重新回到20万以上。具体数据: 美国4月非农就业人口变动 26.3万人,预期 19万人,前值 19.6万人。 美国4月失业率 3.6%,预期 3.8%,前值 3…...

    2024/5/4 23:54:56
  8. 【原油贵金属早评】库存继续增加,油价收跌

    原标题:【原油贵金属早评】库存继续增加,油价收跌周三清晨公布美国当周API原油库存数据,上周原油库存增加281万桶至4.692亿桶,增幅超过预期的74.4万桶。且有消息人士称,沙特阿美据悉将于6月向亚洲炼油厂额外出售更多原油,印度炼油商预计将每日获得至多20万桶的额外原油供…...

    2024/5/9 4:20:59
  9. 【外汇早评】日本央行会议纪要不改日元强势

    原标题:【外汇早评】日本央行会议纪要不改日元强势近两日日元大幅走强与近期市场风险情绪上升,避险资金回流日元有关,也与前一段时间的美日贸易谈判给日本缓冲期,日本方面对汇率问题也避免继续贬值有关。虽然今日早间日本央行公布的利率会议纪要仍然是支持宽松政策,但这符…...

    2024/5/4 23:54:56
  10. 【原油贵金属早评】欧佩克稳定市场,填补伊朗问题的影响

    原标题:【原油贵金属早评】欧佩克稳定市场,填补伊朗问题的影响近日伊朗局势升温,导致市场担忧影响原油供给,油价试图反弹。此时OPEC表态稳定市场。据消息人士透露,沙特6月石油出口料将低于700万桶/日,沙特已经收到石油消费国提出的6月份扩大出口的“适度要求”,沙特将满…...

    2024/5/4 23:55:05
  11. 【外汇早评】美欲与伊朗重谈协议

    原标题:【外汇早评】美欲与伊朗重谈协议美国对伊朗的制裁遭到伊朗的抗议,昨日伊朗方面提出将部分退出伊核协议。而此行为又遭到欧洲方面对伊朗的谴责和警告,伊朗外长昨日回应称,欧洲国家履行它们的义务,伊核协议就能保证存续。据传闻伊朗的导弹已经对准了以色列和美国的航…...

    2024/5/4 23:54:56
  12. 【原油贵金属早评】波动率飙升,市场情绪动荡

    原标题:【原油贵金属早评】波动率飙升,市场情绪动荡因中美贸易谈判不安情绪影响,金融市场各资产品种出现明显的波动。随着美国与中方开启第十一轮谈判之际,美国按照既定计划向中国2000亿商品征收25%的关税,市场情绪有所平复,已经开始接受这一事实。虽然波动率-恐慌指数VI…...

    2024/5/7 11:36:39
  13. 【原油贵金属周评】伊朗局势升温,黄金多头跃跃欲试

    原标题:【原油贵金属周评】伊朗局势升温,黄金多头跃跃欲试美国和伊朗的局势继续升温,市场风险情绪上升,避险黄金有向上突破阻力的迹象。原油方面稍显平稳,近期美国和OPEC加大供给及市场需求回落的影响,伊朗局势并未推升油价走强。近期中美贸易谈判摩擦再度升级,美国对中…...

    2024/5/4 23:54:56
  14. 【原油贵金属早评】市场情绪继续恶化,黄金上破

    原标题:【原油贵金属早评】市场情绪继续恶化,黄金上破周初中国针对于美国加征关税的进行的反制措施引发市场情绪的大幅波动,人民币汇率出现大幅的贬值动能,金融市场受到非常明显的冲击。尤其是波动率起来之后,对于股市的表现尤其不安。隔夜美国股市出现明显的下行走势,这…...

    2024/5/6 1:40:42
  15. 【外汇早评】美伊僵持,风险情绪继续升温

    原标题:【外汇早评】美伊僵持,风险情绪继续升温昨日沙特两艘油轮再次发生爆炸事件,导致波斯湾局势进一步恶化,市场担忧美伊可能会出现摩擦生火,避险品种获得支撑,黄金和日元大幅走强。美指受中美贸易问题影响而在低位震荡。继5月12日,四艘商船在阿联酋领海附近的阿曼湾、…...

    2024/5/4 23:54:56
  16. 【原油贵金属早评】贸易冲突导致需求低迷,油价弱势

    原标题:【原油贵金属早评】贸易冲突导致需求低迷,油价弱势近日虽然伊朗局势升温,中东地区几起油船被袭击事件影响,但油价并未走高,而是出于调整结构中。由于市场预期局势失控的可能性较低,而中美贸易问题导致的全球经济衰退风险更大,需求会持续低迷,因此油价调整压力较…...

    2024/5/8 20:48:49
  17. 氧生福地 玩美北湖(上)——为时光守候两千年

    原标题:氧生福地 玩美北湖(上)——为时光守候两千年一次说走就走的旅行,只有一张高铁票的距离~ 所以,湖南郴州,我来了~ 从广州南站出发,一个半小时就到达郴州西站了。在动车上,同时改票的南风兄和我居然被分到了一个车厢,所以一路非常愉快地聊了过来。 挺好,最起…...

    2024/5/7 9:26:26
  18. 氧生福地 玩美北湖(中)——永春梯田里的美与鲜

    原标题:氧生福地 玩美北湖(中)——永春梯田里的美与鲜一觉醒来,因为大家太爱“美”照,在柳毅山庄去寻找龙女而错过了早餐时间。近十点,向导坏坏还是带着饥肠辘辘的我们去吃郴州最富有盛名的“鱼头粉”。说这是“十二分推荐”,到郴州必吃的美食之一。 哇塞!那个味美香甜…...

    2024/5/4 23:54:56
  19. 氧生福地 玩美北湖(下)——奔跑吧骚年!

    原标题:氧生福地 玩美北湖(下)——奔跑吧骚年!让我们红尘做伴 活得潇潇洒洒 策马奔腾共享人世繁华 对酒当歌唱出心中喜悦 轰轰烈烈把握青春年华 让我们红尘做伴 活得潇潇洒洒 策马奔腾共享人世繁华 对酒当歌唱出心中喜悦 轰轰烈烈把握青春年华 啊……啊……啊 两…...

    2024/5/8 19:33:07
  20. 扒开伪装医用面膜,翻六倍价格宰客,小姐姐注意了!

    原标题:扒开伪装医用面膜,翻六倍价格宰客,小姐姐注意了!扒开伪装医用面膜,翻六倍价格宰客!当行业里的某一品项火爆了,就会有很多商家蹭热度,装逼忽悠,最近火爆朋友圈的医用面膜,被沾上了污点,到底怎么回事呢? “比普通面膜安全、效果好!痘痘、痘印、敏感肌都能用…...

    2024/5/5 8:13:33
  21. 「发现」铁皮石斛仙草之神奇功效用于医用面膜

    原标题:「发现」铁皮石斛仙草之神奇功效用于医用面膜丽彦妆铁皮石斛医用面膜|石斛多糖无菌修护补水贴19大优势: 1、铁皮石斛:自唐宋以来,一直被列为皇室贡品,铁皮石斛生于海拔1600米的悬崖峭壁之上,繁殖力差,产量极低,所以古代仅供皇室、贵族享用 2、铁皮石斛自古民间…...

    2024/5/8 20:38:49
  22. 丽彦妆\医用面膜\冷敷贴轻奢医学护肤引导者

    原标题:丽彦妆\医用面膜\冷敷贴轻奢医学护肤引导者【公司简介】 广州华彬企业隶属香港华彬集团有限公司,专注美业21年,其旗下品牌: 「圣茵美」私密荷尔蒙抗衰,产后修复 「圣仪轩」私密荷尔蒙抗衰,产后修复 「花茵莳」私密荷尔蒙抗衰,产后修复 「丽彦妆」专注医学护…...

    2024/5/4 23:54:58
  23. 广州械字号面膜生产厂家OEM/ODM4项须知!

    原标题:广州械字号面膜生产厂家OEM/ODM4项须知!广州械字号面膜生产厂家OEM/ODM流程及注意事项解读: 械字号医用面膜,其实在我国并没有严格的定义,通常我们说的医美面膜指的应该是一种「医用敷料」,也就是说,医用面膜其实算作「医疗器械」的一种,又称「医用冷敷贴」。 …...

    2024/5/9 7:32:17
  24. 械字号医用眼膜缓解用眼过度到底有无作用?

    原标题:械字号医用眼膜缓解用眼过度到底有无作用?医用眼膜/械字号眼膜/医用冷敷眼贴 凝胶层为亲水高分子材料,含70%以上的水分。体表皮肤温度传导到本产品的凝胶层,热量被凝胶内水分子吸收,通过水分的蒸发带走大量的热量,可迅速地降低体表皮肤局部温度,减轻局部皮肤的灼…...

    2024/5/4 23:54:56
  25. 配置失败还原请勿关闭计算机,电脑开机屏幕上面显示,配置失败还原更改 请勿关闭计算机 开不了机 这个问题怎么办...

    解析如下&#xff1a;1、长按电脑电源键直至关机&#xff0c;然后再按一次电源健重启电脑&#xff0c;按F8健进入安全模式2、安全模式下进入Windows系统桌面后&#xff0c;按住“winR”打开运行窗口&#xff0c;输入“services.msc”打开服务设置3、在服务界面&#xff0c;选中…...

    2022/11/19 21:17:18
  26. 错误使用 reshape要执行 RESHAPE,请勿更改元素数目。

    %读入6幅图像&#xff08;每一幅图像的大小是564*564&#xff09; f1 imread(WashingtonDC_Band1_564.tif); subplot(3,2,1),imshow(f1); f2 imread(WashingtonDC_Band2_564.tif); subplot(3,2,2),imshow(f2); f3 imread(WashingtonDC_Band3_564.tif); subplot(3,2,3),imsho…...

    2022/11/19 21:17:16
  27. 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机...

    win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”问题的解决方法在win7系统关机时如果有升级系统的或者其他需要会直接进入一个 等待界面&#xff0c;在等待界面中我们需要等待操作结束才能关机&#xff0c;虽然这比较麻烦&#xff0c;但是对系统进行配置和升级…...

    2022/11/19 21:17:15
  28. 台式电脑显示配置100%请勿关闭计算机,“准备配置windows 请勿关闭计算机”的解决方法...

    有不少用户在重装Win7系统或更新系统后会遇到“准备配置windows&#xff0c;请勿关闭计算机”的提示&#xff0c;要过很久才能进入系统&#xff0c;有的用户甚至几个小时也无法进入&#xff0c;下面就教大家这个问题的解决方法。第一种方法&#xff1a;我们首先在左下角的“开始…...

    2022/11/19 21:17:14
  29. win7 正在配置 请勿关闭计算机,怎么办Win7开机显示正在配置Windows Update请勿关机...

    置信有很多用户都跟小编一样遇到过这样的问题&#xff0c;电脑时发现开机屏幕显现“正在配置Windows Update&#xff0c;请勿关机”(如下图所示)&#xff0c;而且还需求等大约5分钟才干进入系统。这是怎样回事呢&#xff1f;一切都是正常操作的&#xff0c;为什么开时机呈现“正…...

    2022/11/19 21:17:13
  30. 准备配置windows 请勿关闭计算机 蓝屏,Win7开机总是出现提示“配置Windows请勿关机”...

    Win7系统开机启动时总是出现“配置Windows请勿关机”的提示&#xff0c;没过几秒后电脑自动重启&#xff0c;每次开机都这样无法进入系统&#xff0c;此时碰到这种现象的用户就可以使用以下5种方法解决问题。方法一&#xff1a;开机按下F8&#xff0c;在出现的Windows高级启动选…...

    2022/11/19 21:17:12
  31. 准备windows请勿关闭计算机要多久,windows10系统提示正在准备windows请勿关闭计算机怎么办...

    有不少windows10系统用户反映说碰到这样一个情况&#xff0c;就是电脑提示正在准备windows请勿关闭计算机&#xff0c;碰到这样的问题该怎么解决呢&#xff0c;现在小编就给大家分享一下windows10系统提示正在准备windows请勿关闭计算机的具体第一种方法&#xff1a;1、2、依次…...

    2022/11/19 21:17:11
  32. 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”的解决方法...

    今天和大家分享一下win7系统重装了Win7旗舰版系统后&#xff0c;每次关机的时候桌面上都会显示一个“配置Windows Update的界面&#xff0c;提示请勿关闭计算机”&#xff0c;每次停留好几分钟才能正常关机&#xff0c;导致什么情况引起的呢&#xff1f;出现配置Windows Update…...

    2022/11/19 21:17:10
  33. 电脑桌面一直是清理请关闭计算机,windows7一直卡在清理 请勿关闭计算机-win7清理请勿关机,win7配置更新35%不动...

    只能是等着&#xff0c;别无他法。说是卡着如果你看硬盘灯应该在读写。如果从 Win 10 无法正常回滚&#xff0c;只能是考虑备份数据后重装系统了。解决来方案一&#xff1a;管理员运行cmd&#xff1a;net stop WuAuServcd %windir%ren SoftwareDistribution SDoldnet start WuA…...

    2022/11/19 21:17:09
  34. 计算机配置更新不起,电脑提示“配置Windows Update请勿关闭计算机”怎么办?

    原标题&#xff1a;电脑提示“配置Windows Update请勿关闭计算机”怎么办&#xff1f;win7系统中在开机与关闭的时候总是显示“配置windows update请勿关闭计算机”相信有不少朋友都曾遇到过一次两次还能忍但经常遇到就叫人感到心烦了遇到这种问题怎么办呢&#xff1f;一般的方…...

    2022/11/19 21:17:08
  35. 计算机正在配置无法关机,关机提示 windows7 正在配置windows 请勿关闭计算机 ,然后等了一晚上也没有关掉。现在电脑无法正常关机...

    关机提示 windows7 正在配置windows 请勿关闭计算机 &#xff0c;然后等了一晚上也没有关掉。现在电脑无法正常关机以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容&#xff0c;让我们赶快一起来看一下吧&#xff01;关机提示 windows7 正在配…...

    2022/11/19 21:17:05
  36. 钉钉提示请勿通过开发者调试模式_钉钉请勿通过开发者调试模式是真的吗好不好用...

    钉钉请勿通过开发者调试模式是真的吗好不好用 更新时间:2020-04-20 22:24:19 浏览次数:729次 区域: 南阳 > 卧龙 列举网提醒您:为保障您的权益,请不要提前支付任何费用! 虚拟位置外设器!!轨迹模拟&虚拟位置外设神器 专业用于:钉钉,外勤365,红圈通,企业微信和…...

    2022/11/19 21:17:05
  37. 配置失败还原请勿关闭计算机怎么办,win7系统出现“配置windows update失败 还原更改 请勿关闭计算机”,长时间没反应,无法进入系统的解决方案...

    前几天班里有位学生电脑(windows 7系统)出问题了&#xff0c;具体表现是开机时一直停留在“配置windows update失败 还原更改 请勿关闭计算机”这个界面&#xff0c;长时间没反应&#xff0c;无法进入系统。这个问题原来帮其他同学也解决过&#xff0c;网上搜了不少资料&#x…...

    2022/11/19 21:17:04
  38. 一个电脑无法关闭计算机你应该怎么办,电脑显示“清理请勿关闭计算机”怎么办?...

    本文为你提供了3个有效解决电脑显示“清理请勿关闭计算机”问题的方法&#xff0c;并在最后教给你1种保护系统安全的好方法&#xff0c;一起来看看&#xff01;电脑出现“清理请勿关闭计算机”在Windows 7(SP1)和Windows Server 2008 R2 SP1中&#xff0c;添加了1个新功能在“磁…...

    2022/11/19 21:17:03
  39. 请勿关闭计算机还原更改要多久,电脑显示:配置windows更新失败,正在还原更改,请勿关闭计算机怎么办...

    许多用户在长期不使用电脑的时候&#xff0c;开启电脑发现电脑显示&#xff1a;配置windows更新失败&#xff0c;正在还原更改&#xff0c;请勿关闭计算机。。.这要怎么办呢&#xff1f;下面小编就带着大家一起看看吧&#xff01;如果能够正常进入系统&#xff0c;建议您暂时移…...

    2022/11/19 21:17:02
  40. 还原更改请勿关闭计算机 要多久,配置windows update失败 还原更改 请勿关闭计算机,电脑开机后一直显示以...

    配置windows update失败 还原更改 请勿关闭计算机&#xff0c;电脑开机后一直显示以以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容&#xff0c;让我们赶快一起来看一下吧&#xff01;配置windows update失败 还原更改 请勿关闭计算机&#x…...

    2022/11/19 21:17:01
  41. 电脑配置中请勿关闭计算机怎么办,准备配置windows请勿关闭计算机一直显示怎么办【图解】...

    不知道大家有没有遇到过这样的一个问题&#xff0c;就是我们的win7系统在关机的时候&#xff0c;总是喜欢显示“准备配置windows&#xff0c;请勿关机”这样的一个页面&#xff0c;没有什么大碍&#xff0c;但是如果一直等着的话就要两个小时甚至更久都关不了机&#xff0c;非常…...

    2022/11/19 21:17:00
  42. 正在准备配置请勿关闭计算机,正在准备配置windows请勿关闭计算机时间长了解决教程...

    当电脑出现正在准备配置windows请勿关闭计算机时&#xff0c;一般是您正对windows进行升级&#xff0c;但是这个要是长时间没有反应&#xff0c;我们不能再傻等下去了。可能是电脑出了别的问题了&#xff0c;来看看教程的说法。正在准备配置windows请勿关闭计算机时间长了方法一…...

    2022/11/19 21:16:59
  43. 配置失败还原请勿关闭计算机,配置Windows Update失败,还原更改请勿关闭计算机...

    我们使用电脑的过程中有时会遇到这种情况&#xff0c;当我们打开电脑之后&#xff0c;发现一直停留在一个界面&#xff1a;“配置Windows Update失败&#xff0c;还原更改请勿关闭计算机”&#xff0c;等了许久还是无法进入系统。如果我们遇到此类问题应该如何解决呢&#xff0…...

    2022/11/19 21:16:58
  44. 如何在iPhone上关闭“请勿打扰”

    Apple’s “Do Not Disturb While Driving” is a potentially lifesaving iPhone feature, but it doesn’t always turn on automatically at the appropriate time. For example, you might be a passenger in a moving car, but your iPhone may think you’re the one dri…...

    2022/11/19 21:16:57