I'm very new to Spring/Springboot and have seen different approaches in tutorials regarding the model classes used to represent database objects. I was just wondering when it's appropriate to use which?
Approach 1:
A basic class to model a user object
public class User {
private final UUID id;
// other fields
public User(UUID id, <other fields>) {
this.id = id;
// set other fields
}
In the repository layer, we might have a DAO which looks something like
#Repository
public interface UserDao {
public int createUser(UUID id, <other fields>);
// other CRUD operations
}
When the user doesn't input a valid UUID (or absent) a default method could insert it by calling UUID.randomUUID()
Approach 2:
Instead of using a UUID as a unique identifier, instead, with something like Hibernate/JPA we use the #Entity annotation on the User class in the model package, and have the PK field annotated with #Id
#Entity
public class User {
#Id
private final long id;
// other fields
}
#Id annotation is the most commonly used approach in Hibernate. This will map a Java String / BigDecimal / long attribute to an identifier. And using this, you can use specify four generation strategies - AUTO, IDENTITY, SEQUENCE and TABLE.
UUIDs are used when you want your primary key to be globally unique. I can think of a few scenarios where you might want this -
You have data in multiple databases and your keys needs to be unique across different databases.
You need your generated id value even before you persist your record in your database for specific business purposes.
But the downside is that, UUIDs are long and may cost more in terms of storage space.
Related
consider an entity with just an id and text field:
#lombok.Data
class Entity {
#javax.persistence.Id
UUID id;
String name;
}
consider that the table definition is as follows:
create table entity (
id uniqueidentifier not null primary key default newid(),
name varchar(max)
);
I am then curious why this doesn't work and how i could make it work:
UUID savedId = entityRepository.save(new Entity().setName("entity name")).getId();
In JPA, entity IDs can be either assigned by the application code, or generated (by the JPA provider, such as Hibernate, or by the database). In many situations, it's desirable to have the entity IDs be generated instead of applicaiton-assigned; it seems like that's what you are expecting.
If so, you need to annotate the id field with #GeneratedValue. For example:
class Entity {
#Id
#GeneratedValue
UUID id;
String name;
}
Note that there are considerations to be made regarding the generation strategy, so you'll want to educate yourself about them and make the right choice based on your situation. This is a good page that discusses the options. This SO Answer also is worth reading (the author is a well-known expert on JPA and Hibernate).
I'm working on a REST API using Spring. I have this class, which id's is being generated automatically:
#Entity
public class Seller implements Serializable{
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
private double tasa;
public Long getId() {
return id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getTasa() {
return tasa;
}
public void setTasa(double tasa) {
this.tasa = tasa;
}
}
I added some endpoints to create, delete and get a seller from the DB. So my problem arises when I delete one seller from the DB. When I try to create a new one, I was expecting to get the lower available value for the id but what is actually doing is using some kind of counter/sequence. Let me show you:
So in my second post instruction I was expecting a json with id = 1, instead I received a 2. I tried using TABLE and IDENTITY strategies but the unwanted behavior continued. So my question is: how can I achieve the behavior I desire? I don´t want gaps between my seller's ids.
In general the database are designed to be incremental. When the ID is generated, it is not generated based on the content of the tables. instead of it, the ID is generated using a sequence. In your example you have some records, but imagine a database with a lot of records. The database generates the IDs based on a Sequence (or similar), to avoid read the data, an expensive process.
If the ID is not relevant to the business, then this behavior doesn't affect your process. (Like the message's id in a chat).
If the ID is important, I recommend to redefine the delete process. you probably need to preserve all the ids, like a customer id.
If you want to preserve the sequence and allow delete records, the recommendation is to generate the id by yourself, but you need to lead with problems like concurrence
I tried using TABLE and IDENTITY strategies but the unwanted behavior continued.
This is not unwanted behaviour. Check
How primary keys are generated.
So my question is: how can I achieve the behavior I desire? I don´t want gaps between my seller's ids
One way to achieve this is to not use #GeneratedValue(strategy = GenerationType.AUTO) and set id manually from program and in there you can put any logic you want.
It's not recommended to set primary key manually. If you want you can use any other field like seller_code for this behaviour.
Another question here which is similar to this.
I'm using Spring-data-Jpa where I've an entity
#Entity(name="person")
public class Person implements Serializable {
#javax.persistence.Id
private long dbId;
#Id
private final String id;
// others attributes removed
}
In above class I've two different ids id (marked with org.springframework.data.annotation.Id) and dbId(marked with javax.persistence.Id) , since my id field is always populated with a unique identifier (for Person class which I'm getting from somewhere else) so while using Spring JpaRepository it always tries to update the record and since it's not in db, nothing happens.
I've debug code and saw that it uses SimpleKeyValueRepository which gets the id field which is id, and thus it always gets a value and tries to update record, can I override this behavior to use dbId instead of id field? Is there any way to achieve same with some configuration or annotation, any help is greatly appreciated.
Each entity must have exactly one #Id. On the other hand, you might want to declare a column as unique. It can be done by:
#Entity(name="person")
public class Person implements Serializable {
#Id
private Long id;
#Column(unique = true)
private final String uuid;
// others attributes removed
}
Also remember, that Spring Data JPA id should be reference Long instead of a primitive as you want to save objects with id = null.
String id should probably be String uuid and be initialized as String uuid = UUID.randomUUID().toString();
Similar situation would be an unique email requirement for user. On one hand it'll be a primary key, but on the other, you won't mark it as #Id.
If you need further clarification or your environment is more complicated, just ask in comments section below.
I'm developing an Java-application which stores its data via Hibernate in a database.
One feature of this application is to define templates like types, etc. for reuse. For instance the type has attributes and you can create instances of an type, which has values for the attributes.
The problem is, that I don't know how to ensure that only values for attributes can assigned which the type defines. In my solution there is a redundancy which cause the problem, but I don't know how to remove it.
My current (and problematic) approach looks like this:
#Entity
class Type
{
#Id
#Generated
private Long id;
#OneToMany(mappedBy="type")
private List<Attribute> attributes;
//...
}
#Entity
class Attribute
{
#Id
#Generated
private Long id;
#ManyToOne
private Type type;
//...
}
#Entity
class Instance
{
#Id
#Generated
private Long id;
#ManyToOne
private Type type;
//...
}
#Entity
class AttributeValue
{
#Id
#Embedded
private ResourceAttributValueId id;
#Column(name="val")
private String value;
//...
}
#Embeddable
public class ResourceAttributValueId implements Serializable
{
#ManyToOne
private ResourceStateImpl resource;
#ManyToOne
private ResourceAttributeImpl attribute;
//...
}
There the definition of the type is redundant: Type can be reached via AttributeValue->Attribute->Type and AttributeValue->Instance->Type
Another idea was to use type + attribute name as id of the attribute and instance + attribute name as id of the attribute value, but that doesn't solves my problem.
The key for correctly modeling "diamond-shaped" dependencies like this is the usage of identifying relationships:
(I took a liberty of renaming your entities slightly, to what I believe is a more consistent naming scheme.)
Note how we migrate the TYPE_ID from the top of the diamond, down both sides, all the way to the bottom and then merge it there. So, since there is only one ATTRIBUTE_INSTANCE.TYPE_ID field and is involved in both FKs, we can never have an attribute instance whose attribute type's type differs from instance's type.
While this avoids "mismatched" attributes, it still doesn't ensure the presence of attribute instances (if you support the concept of "required attribute"), which is best enforced at the application level. Theoretically you could enforce it at the database level, using circular deferred FKs, but not all DBMSes support that, and I doubt it would play nicely with ORMs.
Unfortunately, I'm not experienced enough with Hibernate to answer whether this can be mapped there and how.
See also:
Choosing from multiple candidate keys
How to keep foreign key relations consistent in a “diamond-shaped” system of relationships
I'm currently using Eclipselink, but I know now days most JPA implementations have been pretty standardized. Is there a native way to map a JPA entity to a view? I am not looking to insert/update, but the question is really how to handle the #Id annotation. Every entity in the JPA world must have an ID field, but many of the views I have created do not conform to this. Is there native support for this in the JPA or do I need to use hacks to get it to work? I've searched a lot and found very little information about doing this.
While using the #Id annotation with fields of directly supported types is not the only way to specify an entity's identity (see #IdClass with multiple #Id annotations or #EmbeddedId with #Embedded), the JPA specification requires a primary key for each entity.
That said, you don't need entities to use JPA with database views. As mapping to a view is no different from mapping to a table from an SQL perspective, you could still use native queries (createNativeQuery on EntityManager) to retrieve scalar values instead.
I've been looking into this myself, and I've found a hack that I'm not 100% certain works but that looks promising.
In my case, I have a FK column in the view that can effectively function as a PK -- any given instance of that foreign object can only occur once in the view. I defined two objects off of that one field: one is designated the ID and represents the raw value of the field, and the other is designated read-only and represents the object being referred to.
#Id
#Column(name = "foreignid", unique = true, nullable = false)
public Long getForeignId() {
...
#OneToOne
#JoinColumn(name = "foreignid", insertable=false, updatable=false)
public ForeignObject getForeignObject() {
...
Like I said, I'm not 100% sure on this one (and I'll just delete this answer if it turns out not to work), but it got my code past a particular crash point.
Dunno if it applies to your specific situation, though. And there's an excellent chance that after 11 months, you no longer care. :-) What the hell, that "Necromancer" badge doesn't just earn itself....
In my view I have a "unique" id, so I mapped it as the Entity id.
It works very well:
#Entity
#Table(name="table")
#NamedQuery(name="Table.findAll", query="SELECT n FROM Table n")
public class Table implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#Column(name="column_a")
private int columnA;
JPA - 2.5.4
CREATE MATERIALIZED VIEW IF NOT EXISTS needed_article as select product_id, count(product_id) as count from product_article group by product_id;
CREATE MATERIALIZED VIEW IF NOT EXISTS available_article as select product_id, count(product_id) as count from article a inner join product_article p
on a.id = p.article_id and a.stock >= p.amount_of group by product_id;
CREATE UNIQUE INDEX productId_available_article ON available_article (product_Id);
CREATE UNIQUE INDEX productId_needed_article ON needed_article (product_Id);
Entity.java
#Entity
#Immutable // hibernate import
#Getter
#Setter
public class NeededArticle {
#Id
Integer productId;
Integer count;
}
Repository.java
#Repository
public interface AvailableProductRepository extends CrudRepository<AvailableArticle, Integer> {
#Query("select available.productId from AvailableArticle available, NeededArticle needed where available.productId = needed.productId and available.count = needed.count")
List<Integer> availableProduct();