Lombok: publicly immutable (over setters) object with all and no arguments constructor, hash, equals and toString - jpa

We use Lombok for our entities to generate that common boilerplate, like constructors, hash/equals and toString.
In the same time we'd like to keep our objects immutable. Unfortunately, we can't make the completely immutable (e.g. with final properties) because JPA/Hibernate processors required no-args constructor and sets properties over reflection.
#lombok.Data doesn't fit because it creates public setters
#lombok.Value doesn't fit because it makes properties final and Hibernate can't set them over reflection.
what really fits us is:
#Getter
#AllArgsConstructor
#NoArgsConstructor
#EqualsAndHashCode
#ToString
#Entity
public class Company {
#Id
private int id;
private String name;
}
But this again creates a boilerplate for us, copy-pasting 5 annotations every time and messing the code.
Unfortunately i have not found any way in Lombok to aggregate annotations to some meta-annotation, like in Spring.
Question: is there any out-of-box annotation in Lombok to generate such publicly immutable entities?
Or
is there any way to declare local meta-annotation?

You should be able to use a slightly lighter version:
#Data
#Setter(AccessLevel.NONE)
#Entity
public class Company {
#Id
private int id;
private String name;
}
with the following in lombok.config file:
lombok.noArgsConstructor.extraPrivate = true
I'm not sure whether the extraPrivate configuration works in Lombok 1.18.0. It should, according to the changelog, but I was unable to make it work in a quick attempt.

You may still use #Data annotations, but provide private setters
#Data
#AllArgsConstructor
#NoArgsConstructor
#Entity
public class Company {
#Id
#Setter(AccessLevel.PRIVATE)
private int id;
#Setter(AccessLevel.PRIVATE)
private String name;
}

Related

Why JPQL ignore parent's fields?

App's stack: Hibernate, Spring Data, JPA.
There are some entities in the app. I try make JPQL-query in repository of my class OpenParagraph.
OpenParagraph:
#Entity
#Table(name = "open_paragraphs")
#NoArgsConstructor
#AllArgsConstructor
#Getter
#Setter
#ToString
public class OpenParagraph extends ProgramEntry {
#NotNull
#Column(name = "sort_num")
private Integer sortNum;
}
OpenParagraph has a parent: abstract class ProgramEntry.
ProgramEntry:
#MappedSuperclass
#NoArgsConstructor
#AllArgsConstructor
#Getter
#Setter
#ToString
public abstract class ProgramEntry extends AbstractBaseEntity {
#NotNull
#ManyToOne
#JoinColumn(name = "paragraph_id")
private Paragraph paragraph;
#NotNull
#ManyToOne
#JoinColumn(name = "program_id")
private Program program;
}
So, i tring to appeal to OpenParagraph's field "Paragraph", but IDEA tells me it's mistake:
It doesn't offer me the "program" field:
IDEA offer fields only from OpenParagraph, not from parent.
My question: this is IDEA's fail? If this is'nt IDEA's fail, then how i can call "program" in this query?
This is/was a bug of Intellij IDEA (maybe related to this?). But:
It is possible to query by fields of the super class (or MappedSuperclass). Here is an example:
#MappedSuperclass
#Getter
#Setter
public class Foo extends AbstractPersistable<Long> {
#Column
private String fooValue;
}
#Entity
#Getter
#Setter
public class Bar extends Foo {
#Column
private String barValue;
}
public interface BarRepository extends JpaRepository<Bar, Long> {
#Query("SELECT b FROM Bar b WHERE b.fooValue = ?1")
List<Bar> findByFooValue(String fooValue);
}
Given this, calling the repository method, something like this will be logged (with enabled sql logging):
Hibernate: select bar0_.id as id1_0_, bar0_.foo_value as foo_valu2_0_, bar0_.bar_value as bar_valu3_0_ from bar bar0_ where bar0_.foo_value=?
Hint:
If you are using Spring Boot (with the test dependency/dependencies and an embedded test db like h2), it is quite easy to execute such methods without to run the whole application. Here just a small snipped that would execute the method (even though this is no test, but that's enough to call methods somehow):
#SpringBootTest
public class BarRepositoryTest {
#Autowired
BarRepository barRepository;
#Test
public void testFindByFooValue() {
barRepository.findByFooValue("foo");
}
}

Querying revisions of nested object using spring-data-envers

I'm trying to implement entity auditing in my Java Spring Boot project using spring-data-envers. All the entities are being created as they should, but I've come up against a brick wall when executing the query.
parentRepository.findRevisions(id).stream().map(Parent::getEntity).collect(Collectors.toList());
During this select the repository is supposed to fetch info also from the child entity, instead I get unable to find <child object> with {id}.
According to my experiments categoryId is being searched in the Category_Aud table, instead of the actual table with desired data.
Code snippets:
#Data
#Entity
#Audited
#NoArgsConstructor
public class Parent {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Enumerated(EnumType.STRING)
private Status status;
#Enumerated(EnumType.STRING)
private Type requestType;
private String fullName;
#ManyToOne
#JoinColumn(name = "child_id")
private Child child;
}
#Data
#Entity
#Audited
#NoArgsConstructor
public class Child {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String description;
}
I've extended Parent with RevisionRepository
#Repository
public interface ParentRepository extends RevisionRepository<Parent, Long, Long>, JpaRepository<Parent, Long>
And annotated my SpringBootApplication entry class with:
#EnableJpaRepositories(repositoryFactoryBeanClass = EnversRevisionRepositoryFactoryBean.class)
I couldn't find any explanation for this so far, how can make parentRepository get what I need?
The underlying problem here is that the reference from a versioned entity isn't really properly defined. Which variant of the reference should be returned? The one at the start of the version you use as a basis, the one at the end? The one that exists right now?
There are scenarios for which each variant makes sense.
Therefor you have to query the revisions yourself and can't simply navigate to them.

Repository findIn confusion

My database has an Exchanges class which contains a list of CurrencyPairs.
Is it possible to use to use a Repository method to directly obtain a CurrencyPair which matches on name within a given Exchange? I'm thinking of something like
CurrencyPairDbo findByExchangeNameAndCurrencyPairIn(...)
but I can't see quite how to tie it all together. Or do I need to write a custom query for this? And does this need to be in the ExchangeRepository or the CurrencyPairRespository?
#Entity()
#Table(name = "Exchanges")
public class ExchangeDbo {
#Id #GeneratedValue
#Getter private Long id;
#Getter private String exchangeName;
#OneToMany(mappedBy = "exchange",
cascade = CascadeType.ALL,
orphanRemoval = true,
fetch = FetchType.EAGER)
#BatchSize(size=100)
#Getter private List<CurrencyPairDbo> listCurrencyPair = new ArrayList<>();
...
}
#Entity()
public class CurrencyPairDbo {
#Id #GeneratedValue
#Getter private Long id;
#Column(unique=true)
private String currencyPair;
#ManyToOne(fetch=FetchType.EAGER)
#Getter private ExchangeDbo exchange;
...
}
Edit:
I'm thinking it's not Find...In that I want at all. I think that something like:
List<CurrencyPairDbo> x = exchangeRepository.findByExchangeNameLowercaseAndListCurrencyPairCurrencyPair(exchangeName.toLowerCase(), currencyPair);
might work, except that in returns an Exchange object and a:
org.springframework.core.convert.ConverterNotFoundException: No converter found capable of converting from type [biz.ianw.coindatabase.database.ExchangeDbo] to type [biz.ianw.coindatabase.database.CurrencyPairDbo]
This, in the currency pair repository, seems to do the job.
I added a lower case field for matching purposes and an index for efficiency.
CurrencyPairDbo findByExchangeExchangeNameLowercaseAndCurrencyPairNameLowercase( String exchangeName, String currencyPair );

Generate JPA entities with wrapper classes instead of primitives in Eclipse

I'm currently using Eclipse Luna for J2EE Developers to generate JPA entities from a schema. The generated entities are being created with primitives (int) instead of wrappers (Integer) which is an issue for nullable fields. Is there a way to change this? There doesn't seem to be an option for it in the wizard and I have had no luck in my search so far.
Here is a snippet of a generated class as an example:
#Entity
#Table(name="facility")
#NamedQuery(name="Facility.findAll", query="SELECT f FROM Facility f")
public class Facility implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
private int id;
#Column(name="census_code")
private String censusCode;
#Temporal(TemporalType.TIMESTAMP)
private Date created;
private Timestamp modified;
private String name;
#Column(name="portal_id")
private int portalId;
#Column(name="short_name")
private String shortName;
...
}
Obviously id should remain a primitive but in this case portalId should be using the wrapper class.
Any suggestions are welcomed.

JPA / Hibernate OneToMany Mapping, using a composite PrimaryKey

I'm currently struggling with the right mapping annotations for a scenario using a composite Primary Key Class. First, what I am trying to achieve in words:
I have 2 classes: group and FieldAccessRule. A Group can have many FieldAccessRules, while a FieldAccessRule only has ONE Group assigned. Modling this is not a problem so far (simplified):
public class Group{
...
#OneToMany(mappedBy = "group")
private Set<FieldAccessRule> fieldAccessRules;
...
}
and for the FieldAccessRule:
public class FieldAccessRule {
...
#ManyToOne
#JoinColumn(name = "group_id")
private Group group;
...
}
Now, I decided to use a Composite PK for the FieldAccessRule, because a Rule should be unique for ONE Group and ONE Field. It looks like this:
#Embeddable
public class FieldAccessRulePK implements Serializable{
private String fieldKey;
private Group group;
...
}
And ofc. the FieldAccessRule needs to change to
public class FieldAccessRule {
...
#EmbeddedId
private FieldAccessRulePK fieldAccessRulePK;
...
}
How do I create the right Mapping for the FieldAccessRuleSet of Group now?
Doing it like this, I get :
In attribute 'fieldAccessRules', the "mapped by" value 'group' cannot be resolved to an >attribute on the target entity.
Whats the right way of creating the mapping from Group to A PART of the PrimaryKey?
Edit:
I know found out, that using
public class Group{
...
#OneToMany(mappedBy = "fieldAccessRolePK.group")
private Set<FieldAccessRule> fieldAccessRules;
...
}
is EXACTLY working as expected. It compiles fine, it creates the DB fine and after loading a group with predefined Roles, they are available as expected.
However, Eclipse still says
In attribute 'fieldAccessRules', the "mapped by" value 'fieldAccessRulePK.group' cannot be resolved to an attribute on the target entity.
Im not sure, if it's good to ignore Error and "assume" everythings fine... (I found a post, where it has been said, that a mapping of the pattern attr1.attr2 is supported by Hibernate but not JPA-confirm.)
In your code, EntityManager cannot resolve mappedBy attribute fieldAccessRulePK.group.
Reason
EntityManager assume that FieldAccessRule entity have an attribute name with fieldAccessRulePK.group during the FieldInjection.
According to Java Naming Variables Rule, you cannot name fieldAccessRulePK.group by using characters dot > '.'
Java Naming Variables Rule Reference
All variable names must begin with a letter of the alphabet, an underscore ( _ ), or a dollar sign ($). The rest of the characters may be any of those previously mentioned plus the digits 0-9.
The convention is to always use a letter of the alphabet. The dollar sign and the underscore are discouraged.
One more thing:
Don't use group instance in your FieldAccessRulePK embeddable class. For more reference here.
Try as below :
#Embeddable
public class FieldAccessRulePK implements Serializable{
#Column(name = "FIELD_KEY")
private String fieldKey;
#Column(name = "GROUP_ID")
private String groupId;
}
public class FieldAccessRule {
#EmbeddedId
private FieldAccessRulePK id;
#ManyToOne
#JoinColumn(name = "GROUP_ID", referencedColumnName = "ID")
private Group group;
}
public class Group{
#OneToMany(mappedBy = "group")
private Set<FieldAccessRule> fieldAccessRules;
}
I've fixed a similar problem by modifying the mappedBy attribute of the parent class (using dotted notation)
public class Group{
...
#OneToMany(mappedBy = "fieldAccessRulePK.group")
private Set<FieldAccessRule> fieldAccessRules;
...
}
and by keeping the annotations in the PK class:
#Embeddable
public class FieldAccessRulePK implements Serializable{
private String fieldKey;
#ManyToOne
#JoinColumn(name = "group_id")
private Group group;
...
}