Enterprise-level Java applications often move data back and forth between Java objects and related databases. From manually writing SQL code
There are many methods to implement the mature object relationship ing (ORM) solution. Regardless of the technology used, once the Java object is permanently stored in the database, the identity will become
A complex and difficult-to-manage topic. It may occur that you instantiate two different objects, but they represent the same row in the database. To solve this problem, you may take measures in the persistent
Implement equals () and hashcode () in the object, but it is more skillful to implement these two methods. What makes the problem worse is those traditional ideas (including
Hibernate official documentation) does not necessarily propose the most practical solutions for new projects.
Differences between object identities in virtual machines (VMS) and databases are a breeding ground for problems. In a virtual machine, you do not get the Object ID. You simply hold the direct reference of the object. And behind the scenes, virtual
The host indeed assigns an 8-byte ID to each object, which is the actual reference of the object. When you persistently store objects in the database, the problem arises. Assume that you have created
Person object and store it in the database (we can call it person1 ). Another part of your code reads the data of this person object from the database and instantiates it as another
New person object (we can call it person2 ). Now your memory has two objects mapped to the same row in the database. An object reference can only point to one of them,
We need a method to indicate that the two objects actually represent the same object. This is why (in a virtual machine) the object identity is introduced.
In Java, the object identity is defined by the equals () method (and related hashcode () method) held by each object. Whether the two objects are the same
For example, the equals () method should be able to determine whether they represent the same entity. The hashcode () method is associated with the equals () method because all equal objects should return
Return the same hashcode. By default, the equals () method only compares object references. An object is equal to itself, but not to any other instance. For persistent objects
It is important to rewrite these two methods so that the two objects representing the same row in the database are considered equal. In Java
Work is especially important.
To clarify the different ways to implement equal () and hashcode (), let's consider a simple object person to prepare for persistent storage to the database.
public class Person {
private Long id;
private Integer version;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Integer getVersion() {
return version;
}
public void setVersion(Integer version) {
this.version = version;
}
// person-specific properties and behavior
}
In this example, we follow the best practices for holding both the ID field and version field. The ID field stores the value used as the primary key in the database, while the version field
It is an increment that increases from 0 and changes with each update of the object (this helps us avoid the problem of concurrent updates ). To make it clearer, let's take a look at how hibernate can put this object
Hibernate ing files permanently stored in the database:
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping SYSTEM
"http://hibernate.sourceforge.net/
hibernate-mapping-3.0.dtd">
<class name="Person" table="PERSON">
<id name="id" column="ID"
unsaved-value="null">
<generator class="sequence">
<param name="sequence">PERSON_SEQ</param>
</generator>
</id>
<version name="version" column="VERSION" />
<!-- Map Person-specific properties here. -->
</class>
The Hibernate ing file specifies that the ID field of person represents the ID column in the database (that is, it is the primary key of the person table ). Included in the ID tag
The unsaved-value = "null" attribute tells hibernate to use the ID field to determine whether a person object has been saved before. The ORM framework must rely on this
To determine whether to use the SQL insert clause or update clause when saving an object. In this example, Hibernate assumes that the ID field of a new object is
Null value. The ID is assigned a value only when it is saved for the first time. The generator tag tells hibernate where to obtain the assigned ID when the object is saved for the first time. Here
In this example, Hibernate uses the database sequence as the source of the unique ID. Finally, the version tag tells hibernate to use the version of the person object.
Field for concurrency control. Hibernate will execute an Optimistic Locking Scheme. According to this scheme, Hibernate checks the object version number based on the database version number before saving the object.
Our person object still lacks the implementation of the equals () method and hashcode () method. Since this is a persistent object, we do not want to rely on the default implementation of the two methods
Because the default implementation does not distinguish two different instances that represent the same row in the database. A simple and obvious implementation method is to use the ID field to compare and generate the EQUAL () method.
The result of the hashcode () method.
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || !(o instanceof Person))
return false;
Person other = (Person)o;
if (id == other.getId()) return true;
if (id == null) return false;
// equivalence by id
return id.equals(other.getId());
}
public int hashCode() {
if (id != null) {
return id.hashCode();
} else {
return super.hashCode();
}
}
Unfortunately, this implementation has problems. When we create a person object for the first time, the ID value is null, which means that any two person objects will be
It is considered equal. If we want to create a person object and put it in a set, and then create a completely different person object, we also put it in the same set. In fact
Two person objects cannot be added. This is because set determines that all unsaved objects are the same.
You may try to implement the equals () method using ID (only when ID is set. After all, if both objects are not saved, we can assume they are different objects. This is because they are assigned different primary keys when they are saved to the database.
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || !(o instanceof Person))
return false;
Person other = (Person)o;
// unsaved objects are never equal
if (id == null || other.getId() == null)
return false;
return id.equals(other.getId());
}
There is an implicit problem here. Java
The Collection framework requires the equals () and hashcode () Methods Based on unchanged fields in the collection lifecycle. In other words, when an object
You cannot change the values of equals () and hashcode () in collection. For example, the following program:
Person p = new Person();
Set set = new HashSet();
set.add(p);
System.out.println(set.contains(p));
p.setId(new Long(5));
System.out.println(set.contains(p));
Output result: True False
False is returned for the 2nd call to set. Contains (P), because set can no longer find P. In professional terms, this object is lost by set! This is because when the object is in the set, we change the value of hashcode.
This is a problem when you want to create a domain object that saves other domain objects in set, map, or list. To solve this problem, you must provide one
Implementation of equals () and hashcode (), which ensures that they work correctly before and after the object is saved, and when the object is in memory (Return Value) is not changeable.
Hibernate reference documentation (v. 3) provides the following suggestions:
"Instead of using database identifiers for Equality judgment, you should use the business key (
Key. When a transient object (transient
Object), the database identifier changes. When a transient instance (often used together with a detached instance) is saved in a set, the hash code changes.
Bad set conventions. The attribute of the Business key does not need to be as stable as the database primary key, as long as the stability of the objects in the same set is guaranteed ." (Hibernate reference documentation v. 3.1.1 ).
"We recommend that you implement equals () and hashcode () by determining the business key equality (). The business key equality means that the equals () method can only compare the attributes of the Business key (common candidate key) of instances in the real world ." (Hibernate reference documentation v. 3.1.1 ).
In other words, normal keys are used for equals () and hashcode (), while the proxy keys generated by hibernate are used for object IDs. This requires a correlation between each object.
Business key. However, not every object type has such a key. At this time, you may try to use fields that will change but will not change frequently. This is similar to the idea that the business key does not have to be as stable as the database primary key.
. If the keys do not change during the lifetime of the object's set, it is "good enough. This is a dangerous point of view, because it means that your application may not crash, but the premise is that no one
The specified field is updated under specific circumstances. Therefore, there should be a better solution, which does exist.
Do not allow hibernate to manage your ID.
The attempt to create and maintain the identity definitions of objects and database rows is the root cause of all discussions so far. If we unify all identity forms, these problems will no longer exist. That is to say
For the replacement of centers and Object-centered IDs, we should create a common and entity-specific ID to represent the data entity. This ID should be created when the data is first input. Regardless of the unique number
Whether the data object is stored in the database, whether it is resident in the memory as an object, or stored in media of other formats, this universal ID should be able to recognize it. By using the object specified during the first creation of a Data Object
ID. We can safely return to the original definitions of equals () and hashcode (). They only need to use this ID:
public class Person {
// assign an id as soon as possible
private String id = IdGenerator.createId();
private Integer version;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public Integer getVersion() {
return version;
}
public void setVersion(Integer version) {
this.version = version;
}
// Person-specific fields and behavior here
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || !(o instanceof Person))
return false;
Person other = (Person)o;
if (id == null) return false;
return id.equals(other.getId());
}
public int hashCode() {
if (id != null) {
return id.hashCode();
} else {
return super.hashCode();
}
}
}
In this example, the Object ID is used as the equals () method to determine equal criteria and the hashcode () returns the hash code source. This is much simpler. However, to make it normal
Work. We need two things. First, we need to ensure that each object has an ID value before being saved. In this example, when the ID variable is declared, it is assigned a value. Second, I
You need a way to determine whether the object is new or saved before. In our earliest example, Hibernate checks whether the ID field is null to determine whether the object is new.
. Since the Object ID is never null, it is clear that this method is no longer valid. Configure hibernate to check the version field instead of whether the ID field is null,
We can easily solve this problem. The version field is a more appropriate indicator used to determine whether an object has been saved.
The following is the improved person class hibernate ing file.
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping SYSTEM
"http://hibernate.sourceforge.net/
hibernate-mapping-3.0.dtd">
<class name="Person" table="PERSON">
<id name="id" column="ID">
<generator class="assigned" />
</id>
<version name="version" column="VERSION"
unsaved-value="null" />
<!-- Map Person-specific properties here. -->
</class>
Note that the generator label under ID contains the attribute class = "assigned ". This property tells hibernate that we are not assigned from the database
ID value, but the id value is assigned in the code. Hibernate will simply think that even new unsaved objects have ID values. We also added a genus to the version label.
Sex: unsaved-value = "null ". This property tells hibernate that the version value instead of the ID value is null as the object is newly created
Display. We can also simply tell hibernate to use a negative value as the indicator that the object has not been saved. If you prefer to set the type of the version field to int rather than integer,
This will be useful.
We have obtained many benefits from transferring to the pure Object ID. The implementation of the equals () and hashcode () methods is simpler and easier to read. These methods are no longer prone to errors and
They can work normally with the collection no matter before or after the object is saved. Hibernate also becomes faster, because it does not need
Reads a sequence value from the database. In addition, the newly defined equals () and hashcode () are generic for all objects that contain ID objects. This means we can move these methods
To an abstract parent class. We no longer need to re-implement equals () and hashcode () for each domain object, and we do not need to consider which fields are unique and
Unchanged. We just need to simply extend this abstract parent class. Of course, we do not need to force the domain object to be extended from the parent class, so we define an interface to ensure design flexibility.
public interface PersistentObject {
public String getId();
public void setId(String id);
public Integer getVersion();
public void setVersion(Integer version);
}
public abstract class AbstractPersistentObject
implements PersistentObject {
private String id = IdGenerator.createId();
private Integer version;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public Integer getVersion() {
return version;
}
public void setVersion(Integer version) {
this.version = version;
}
public boolean equals(Object o) {
if (this == o) return true;
if (o == null ||
!(o instanceof PersistentObject)) {
return false;
}
PersistentObject other
= (PersistentObject)o;
// if the id is missing, return false
if (id == null) return false;
// equivalence by id
return id.equals(other.getId());
}
public int hashCode() {
if (id != null) {
return id.hashCode();
} else {
return super.hashCode();
}
}
public String toString() {
return this.getClass().getName()
+ "[id=" + id + "]";
}
}
Now we have a simple and efficient method to create a domain object. They extend abstractpersistentobject, and the parent class can
Assign an ID and properly implement equals () and hashcode (). The domain object also obtains a reasonable default implementation of the tostring () method. This method can include
Selected to be overwritten. If this is a test object or example object of the query example, the ID can be modified or set to null. Otherwise, it should not be changed. For some reason, we need to create
A domain object that extends other classes. This object should implement the persistentobject interface instead of the extension abstract class.
The person class is much simpler now:
public class Person
extends AbstractPersistentObject {
// Person-specific fields and behavior here
}
From the previous example, The Hibernate ing file will not change. We don't want to bother hibernate to understand abstract parent classes. We just need to ensure that every
The persistentobject ing file contains an ID (and an "assigned" generator) and a property with unsaved-value = "null ".
Version label. Smart readers may have noticed that the ID of a persistent object is assigned whenever it is instantiated. This means that when hibernate creates
When an instance of the saved object already exists and is read from the database, it will get a new ID. That's all. Then hibernate will call the object's
Setid () method, replace the newly allocated ID with the saved ID. Generating additional IDS is not a problem because the ID generation algorithm is cheap (that is, it does not involve databases ).
Everything has been fine so far, but we have omitted an important detail: How to Implement idgenerator. createid (). We can define some criteria for the ideal key-generation algorithm:
- Keys can be generated cheaply without being pulled to the database.
- Keys must be unique even if they span different virtual machines and machines.
- If possible, keys can be generated by other programs, programming languages, and databases, but must be at least compatible with them.
What we need is a universal unique identifier (UUID ). UUID consists of 16 bytes (128 bits) of digits and complies with the standard format. The string version of UUID looks similar to the following:
2cdb8cee-9134-440f-9d7a-14c0ae8184c6
The characters in the string are represented in hexadecimal notation, which separates different parts of the number. The format is simple and easy to handle, but 36 characters are a little long. Because the horizontal line is always placed in the same position
So you can remove them to reduce the number of characters to 32. For more concise representation, you can create an array of byte [16] or two 8-byte long strings to save
Some numbers. If you are using Java 1.5 or later, you can directly use the uuid class, although this is not the most concise format in the memory. For more information, see Wikipedia UUID entries and javadoc UUID class entries.
UUID generation algorithms have multiple implementations. Since the final UUID is a standard format, it does not matter which implementation we adopt in the idgenerator class. Since no matter what algorithm is used
IDs are always unique. We can even change the implementation of algorithms or implement different mixed matching at any time. If you are using Java
Java. util. UUID:
public class IdGenerator {
public static String createId() {
UUID uuid = java.util.UUID.randomUUID();
return uuid.toString();
}
}
For those who do not use Java 1.5 or later, there are at least two extension libraries that implement UUID and are compatible with Java versions earlier than 1.5: apache commons ID project and Java UUID generator (jug) project. They are available under Apache license (jug is also available under lgpl ).
This is an example of using the jug library to implement idgenerator:
import org.safehaus.uuid.UUIDGenerator;
public class IdGenerator {
public static final UUIDGenerator uuidGen
= UUIDGenerator.getInstance();
public static String createId() {
UUID uuid
= uuidGen.generateRandomBasedUUID();
return uuid.toString();
}
}
What about the built-in UUID generator Algorithm in hibernate? Is this an appropriate way to obtain the uuid of an object identity? If you want to make the Object Identity independent from the object persistence
Not a good method. Although hibernate does provide the uuid generation option, in this case, we return to the earliest problem: Object IDs are not obtained when they are created.
When they are saved.
The biggest obstacle to using UUID as the primary key of the database is their size in the database (instead of in memory). The combination of indexes and Foreign keys in the database will increase the size of the primary key. You must
Use different representation methods. String indicates that the primary key size of the database is 32 or 36 bytes. The number can also be stored in bytes, reducing the size by half.
Database, the identifier will become difficult to understand. The feasibility of these methods for your project depends on your needs.
If the database does not accept UUID as the primary key, you can consider using the database sequence. However, you should always assign an ID when creating a new object instead of letting hibernate manage the ID. In this
In this case, the business object that creates the new domain object can call a service that uses the data access object (DAO) to retrieve IDs from the database sequence. If a long data type is used to represent the Object ID
A separate database sequence (and service method) is sufficient for your domain object.
Conclusion
When an object is permanently stored in a database, it is difficult to properly implement the Object Identity. Even so, the problem is that objects are allowed to exist without IDs before they are saved. We can
This problem is solved by obtaining the assigned object ID responsibilities from the object link ing framework such as hibernate. Once an object is instantiated, it should be assigned an ID. This changes the Object Identity
This method is simple and error-free, and reduces the amount of code required in the domain model.
Author Profile |
|
James brundege is currently an independent contractor and serves as a consultant in synaptocode software LLC., a company owned by James brundege. |