change discriminator value at runtime? - java

I'm working with a hierachy object model with a jpa entity persistance support.
Here the classes model:
User class:
#Entity
#Table(name = "user", catalog = "users")
#NamedQueries({
#NamedQuery(...
})
#Inheritance(strategy= InheritanceType.JOINED)
#DiscriminatorColumn(name = "apType", discriminatorType =
DiscriminatorType.STRING, length = 255)
//#DiscriminatorValue("user")
public class User implements Serializable {
#Transient
protected PropertyChangeSupport changeSupport = new
PropertyChangeSupport(this);
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private Long id;
#Column(name = "apType")
private String apType;
#Basic(optional = false)
#Column(name = "name")
private String name;
public UsuariDeClaus() {
this.setApType("user");
}
public Long getId() {
return id;
}
public void setId(Long id) {
Long oldId = this.id;
this.id = id;
changeSupport.firePropertyChange("id", oldId, id);
}
public String getApType() {
return apType;
}
public void setApType(String apType) {
this.apType = apType;
}
public String getName() {
return name;
}
public void setName(String name) {
String oldName = this.name;
this.name = name;
changeSupport.firePropertyChange("name", oldName, name);
}
public void addPropertyChangeListener(PropertyChangeListener listener) {
changeSupport.addPropertyChangeListener(listener);
}
public void removePropertyChangeListener(PropertyChangeListener
listener) {
changeSupport.removePropertyChangeListener(listener);
}
}
ApplicationUser class :
#Entity
#Table(name = "applicationuser", catalog = "usuweb793")
#NamedQueries({
#NamedQuery(...
})
public class ApplicationUser extends Users{
#Basic(optional = false)
#Column(name = "nickname", unique=true)
private String nickname;
#Basic(optional = false)
#Column(name = "password")
private String password;
public ApplicationUser() {
super.setApType("ApplicationUser");
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
String oldPassword = this.password;
this.password = password;
changeSupport.firePropertyChange("password", oldPassword, password);
}
public String getNickname() {
return user;
}
public void setNickname(String nickname) {
String oldNickname = this.nickname;
this.nickname = nickname;
changeSupport.firePropertyChange("nickname", oldNickname, nickname);
}
}
and administratorUser class:
#Entity
#Table(name = "administratoruser", catalog = "usuweb793")
#NamedQueries({
#NamedQuery(...})
public class AdministratorUser extends AplicationUser{
public AdministratorUser() {
super.setApType("administratoruser");
}
}
The entity manager creates on mysql database 4 tables:
sequence, user, aplicationuser and administratoruser.
user table:
id name aptype
1 aaa user
2 bbb aplicationuser
3 ccc administratoruser
aplicationuser table:
id nickname password
2 bbbxxxx bbbyyyyy
3 cccxxxx cccyyyyy
administratoruser table:
id
3
Is possible to change the user priviligies without remove an object and create a new one ?
(i would like the id not to change)
Something like:
User user = em.find(1);
New AplicattionUser(user);
user table:
id name aptype
1 aaa aplicationuser
2 bbb aplicationuser
3 ccc administratoruser
aplicationuser table:
id nickname password
1 aaaxxx aaayyyyy
2 bbbxxxx bbbyyyyy
3 cccxxxx cccyyyyy
administratoruser table:
id
3

According to what I understand and from my POV, if Application and Administrator are just roles for Userthat you can switch between .... remove the inheretence, make a User entity and Role entity -with a corresponding table and join between the two entities with the appropriate join -OneToOne or OneToMany according to to your business case- ..... If you can't change the database/the code ... there is a "dirty" solution (and "dirty" again) that you might try ... make a native bulk update statement to make the change you need but be aware of the following :
1- I am nit sure if it will work, you need to try it
2- You must be sure that the entity is not managed by any persistence context at the time of running
3- You are responsible for refreshing the entity / persistence context/ cache after the update

Related

Jpa Repository in Spring boot app findBy issue

I'm trying to create findBy JpaRepo it's about returning only the data where isDeleted attribute is false.
this is my Service :
public List<Customer> getAllCustomers() {
List<Customer> customers = cutomerRepository.findByIsDeletedFalse();
return customers;
}
and this is my Controller :
#GetMapping("/viewList")
#CrossOrigin("http://localhost:4200/")
public ResponseEntity<List<Customer>> getAllCustomers() {
List<Customer> customers = new ArrayList<>();
customers = customerService.getAllCustomers();
if (customers.isEmpty()) {
LOGGER.error("no content ");
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
}
LOGGER.info("calling list of customers");
return new ResponseEntity<>(customers, HttpStatus.OK);
}
and this is customer model :
public class Customer {
#Id
#GeneratedValue(strategy= GenerationType.IDENTITY)
private int id;
#Column(name = "serial_number")
private long serialNumber;
#Column(name = "first_name")
private String firstName;
#Column(name = "last_name")
private String lastName;
#Column(name = "email")
private String email;
#Column(name = "mobile_number")
private String mobileNumber;
#Column(name = "is_deleted")
private boolean isDeleted;
}
but when I run it in postman it's not working and return an error :
Caused by: org.postgresql.util.PSQLException: ERROR: operator does not
exist: boolean = integer Hint: No operator matches the given name
and argument types. You might need to add explicit type casts.
Position: 315
How could I solve this issue?
Looks like the name for your query isn't created right.
However, in this case, the usage of #Query will be much clearer.
Code snippet:
public interface CustomerRepo extends JpaRepository<Customer, Integer> {
List<Customer> findAllByIsDeletedIsFalse();
#Query("from Customer c where c.isDeleted=false")
List<Customer> getAllCustomers();
}
Iinstead of:
cutomerRepository.findByIsDeletedFalse()
You missed one more Is at the name of the method.
Update your Domain:
public class Customer implements Serializable {
private final static long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy= GenerationType.IDENTITY)
private Integer id;
#Column(name = "serial_number")
private Long serialNumber;
// ...
#Column(name = "is_deleted")
private Boolean isDeleted;
}
JPA fields should be Objects instead of primitives. And entity class should implement Serializable as well.
If the exception will be the same you could try to update #Query:
#Query("from Customer c where c.isDeleted=0")
If pure SQL works for your DB you could use native query:
#Query(
value = "select * from Customer where is_deleted = false",
nativeQuery = true)
List<Customer> getAllCustomers();
It's not working because it doesn't follow the naming conventions for a boolean field. Usually in Java the primitive booleans are named without is prefix and the getter would be using this is prefix.
So in your case your entity class should look like that:
public class Customer {
// ...
#Column(name = "is_deleted")
private boolean deleted;
public boolean isDeleted() {
return deleted;
}
public void setDeleted(boolean deleted) {
this.deleted = deleted;
}
}
Also the naming of the spring repository method should be:
List<Customer> findAllByDeletedIsFalse();
In case you want to use a Boolean reference type you can name your field isDeleted, but then the class would look like that:
public class Customer {
// ...
#Column(name = "is_deleted")
private Boolean isDeleted;
public Boolean getIsDeleted() {
return isDeleted;
}
public void setIsDeleted(Boolean isDeleted) {
this.isDeleted = isDeleted;
}
}
and the repository method:
List<Customer> findAllByIsDeletedIsFalse();
Boolean Java maps a bit datatype column. You are probably using int as datatype in your database.

How to manage several entities with JPA?

Context
I'm trying to create a simple API to learn Spring Boot and JPA. Informations are stored into H2 database.
I have 2 entities : mission and user.
Several users are assigned to a mission. And users can be assigned to different missions.
So, my class Mission has an attribute private ArrayList<User> users;.
Objective
My goal is to create some requests as :
(GET request) IP/missions/123456/users : Get all users assigned to mission 123456
(PUT request) IP/missions/456789 : Assigned users to mission 456789
So, tied both entities.
Problematic
But I don't know how to store/tie informations relative to mission and user. The good way is to create an "associative table" with the scheme Assignements(id_user, id_mission) ?
Thanks for help!
Edit 1 : Code
Entity Mission
#Entity
public class Mission{
#Id
private String id;
private String name;
private Date start;
private Date end;
private ArrayList<User> users;
private int status;
public Mission() {
}
public Mission(String name, Date start, Date end) {
this.name = name;
this.start = start;
this.end = end;
this.status = 0;
}
// getters and setters
}
Entity User
#Entity
public class User {
#Id
private String id;
private String name;
public User() {
}
public User(String name) {
this.name = name;
}
// getters and setters
}
SQL
I use a SQL script. Currently, script looks like :
INSERT INTO mission (id, name, start, end, status) VALUES ('de7d9052-4961-4b4f-938a-3cd12cbe1f82', 'mission 1', '2019-02-11', '2019-02-13', 0)
INSERT INTO mission (id, name, start, end, status) VALUES ('425e7701-02c6-4de3-9333-a2459eece1c8', 'mission 2', '2019-02-10', '2019-02-15', 0)
Edit 2 : New code (with #ManyToMany)
Entities
#Entity
public class Mission {
#Id
private String missionid;
private String namemission;
private Date start;
private Date end;
#ManyToMany
#JoinTable(name = "mission_user",
joinColumns = #JoinColumn(name = "missionid"),
inverseJoinColumns = #JoinColumn(name = "userid")
)
private Set<User> users = new HashSet<>();
private int status;
public Mission() {}
public Mission(String name, Date start, Date end) {
this.namemission = name;
this.start = start;
this.end = end;
this.status = 0;
}
}
#Entity
public class User {
#Id
private String iduser;
private String nomuser;
#ManyToMany(mappedBy = "users")
private Set<Mission> missions = new HashSet<>();
public User() {}
public User(String nom) {
this.nomuser = nom;
}
}
Repositories
#RepositoryRestResource(collectionResourceRel = "mission")
public interface MissionResource extends JpaRepository<Mission, String> {
...
}
#RepositoryRestResource(collectionResourceRel = "user")
public interface UserResource extends JpaRepository<User, String> {
...
}
Rest Controller
#RestController
#RequestMapping(value = "/missions", produces = MediaType.APPLICATION_JSON_VALUE)
#ExposesResourceFor(Mission.class)
public class MissionRepresentation {
private final MissionResource missionResource;
private final UserResource userResource;
public MissionRepresentation(MissionResource missionResource, UserResource userResource) {
this.missionResource = missionResource;
this.userResource = userResource;
}
// mapping
}
The best would be to implement a joining table, just as you said. That would be something like:
create table mission_user (
mission_id int,
user_id int,
primary key (mission_id, user_id)
)
Then a #ManyToMany using this table as mapping or entity like MissionUser and map both User and Mission as #OneToMany to it.
The mission class will have:
#Entity
public class Mission {
...
#ManyToMany(mappedBy = "missions")
private List<User> users;
...
}
and User class will have:
#Entity
public class User {
...
#ManyToMany(mappedBy = "users")
private List<Mission> missions;
...
}
this will create a table in your database with name something like: missions_uesrs and it will have missionId and userId as only columns.

Transient field is lost when appending new OneToMany entity

I have two entities which are linked via a OneToMany relationship:
#Entity
#Table(name="bookcase")
public class BookCase {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Transient
#Getter #Setter private Long oldId;
/*
https://vladmihalcea.com/a-beginners-guide-to-jpa-and-hibernate-cascade-types/
*/
#OneToMany(mappedBy = "bookCase", cascade = CascadeType.ALL, orphanRemoval = true)
private Set<Bookshelf> bookShelves = new HashSet<>();
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public Set<Bookshelf> getBookShelves() { return bookShelves; }
public void setBookShelves(Set<Bookshelf> bookShelves) { this.bookShelves = bookShelves; }
}
#Entity
#Table(name="bookshelf")
public class Bookshelf {
private static final Logger log = LoggerFactory.getLogger(Bookshelf.class);
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Transient
#Getter #Setter private Long oldId;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "bookcase_id")
private BookCase bookCase;
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public BookCase getBookCase() { return bookCase; }
public void setBookCase(BookCase bookCase) {
this.bookCase = bookCase;
bookCase.getBookShelves().add(this);
}
#Transient
#Setter private OldIdListener oldIdListener;
/*
When the id is saved, listening DTOs can update their ids
*/
#PostPersist
public void triggerOldId() {
log.info("Postpersist triggered for {}", id);
if (oldIdListener != null) {
oldIdListener.updateId(oldId, id);
}
}
}
public interface OldIdListener {
void updateId(long oldId, long newId);
}
The following test fails:
#Test
public void testThatCascadingListenerIsTriggered() {
var mock = mock(OldIdListener.class);
var mock2 = mock(OldIdListener.class);
var mock3 = mock(OldIdListener.class);
var bookcase = new BookCase();
var shelf1 = new Bookshelf();
shelf1.setOldId(-5L);
shelf1.setBookCase(bookcase);
shelf1.setOldIdListener(mock);
var shelf2 = new Bookshelf();
shelf2.setOldId(-6L);
shelf2.setBookCase(bookcase);
shelf2.setOldIdListener(mock2);
var saved = bookCaseRepository.save(bookcase);
verify(mock).updateId(eq(-5L), anyLong());
verify(mock2).updateId(eq(-6L), anyLong());
var savedBookCase = bookCaseRepository.findById(saved.getId()).get();
assertThat(savedBookCase.getBookShelves()).hasSize(2);
var shelf3 = new Bookshelf();
shelf3.setOldId(-10L);
shelf3.setBookCase(savedBookCase);
shelf3.setOldIdListener(mock3);
savedBookCase.getBookShelves().add(shelf3);
bookCaseRepository.save(savedBookCase);
verify(mock3).updateId(eq(-10L), anyLong());
}
mock3 is never called.
When debugging the code, I can see that the transient fields oldId and oldIdListener are set to null when the #PostPersist method is called on object shelf3, not on shelf1 and 2.
I think this is because I am modifying the Set object; but the object is correctly persisted, it just loses all transient fields. This does not happen when the entire tree is persisted for the first time.
Is this the wrong way to insert a new element to a OneToMany set or where is the error here?
I'm using Spring Boot 2.1.
Thanks!
The field which annotation with #Transient will not persist to the database, so if you want it to persist, you must remove #Transient.

Composite PK with JPA in ORACLE gives ORA-00904 error

I have to map a composite PK with JPA in an Oracle DB.
I've followed other SO questions with relation to this tutorial but I'm still getting the following error:
java.sql.SQLSyntaxErrorException: ORA-00904: "COMPOSITEI0_"."NAME_1": Invalid Identifier (where NAME_1 relates to the name of one of the columns which are part of the PK)
This is my entity (real names not mentioned for data protection reasons):
#Entity
#Table(schema = "SCHEMA", name = "TABLE")
public class CompositeIdEntity {
#Column(name = "NAME1")
private String name1;
#Column(name = "NAME2")
private String name2;
#Column(name = "NAME3")
private String name3;
#EmbeddedId
CompositePrimaryKeyTableEmbeddable id;
public CompositePrimaryKeyTableEmbeddable getId() {
return this.id;
}
public void setId(CompositePrimaryKeyTableEmbeddable id) {
this.id = id;
}
// other getters and setters
My #Embeddable id class:
#Embeddable
public class CompositePrimaryKeyTableEmbeddable implements Serializable{
/**
*
*/
private static final long serialVersionUID = 1L;
#Column(name="name1")
private String name1;
#Column(name="name2")
private String name2;
public CompositePrimaryKeyTableEmbeddable() {
super();
}
public CompositePrimaryKeyTableEmbeddable(String name1, String name2) {
this.name1 = name1;
this.name2 = name2;
}
My #Repository:
#Repository
public interface CompositeIdDao extends JpaRepository<CompositeIdEntity, CompositePrimaryKeyTableEmbeddable> {
}
And finally call to the DB, which only returns null because it's just a test to see if it all works together:
public CompositeIdEto saveCompositeId() {
CompositeIdEntity compositeIdEto = new CompositeIdEntity();
compositeIdEto.setname3("New");
compositeIdEto.setId(new CompositePrimaryKeyTableEmbeddable("ERR", "ER"));
this.compositeIdDao.save(compositeIdEto);
return null;
}
It seems you are duplicating the name1 and name2 columns by declaring them once
in the entity itself and later in the embeddable.
You seem to only need the id embeddable and the name3 declaration in the entity:
#Entity
#Table(schema = "SCHEMA", name = "TABLE")
public class CompositeIdEntity {
#EmbeddedId
CompositePrimaryKeyTableEmbeddable id;
#Column(name = "NAME3")
private String name3;

JPA Null or zero primary key encountered in unit of work clone

I learn about JPA and had task to make database and insert some values to it. I wondered how I can find out what was the ID of recently inserted object, so I found a way that I need to use flush method of EntityManager.
Unfortunately I got the
Null or zero primary key encountered in unit of work clone
exception when I use the above method. I think the problem lies in that my database has all ID's set on autoincrement ( I use ORACLE 11G Express ), so before commiting it has null value and it rollbacks transaction.
What I can do to fix it ?
This is DB ( ID's are autoincrement[Sequences and Triggers in oracle]):
public class Client {
public static void main(String[] args) {
EntityManagerFactory emf =
Persistence.createEntityManagerFactory("JpaIntroductionPU");
EntityManager em = emf.createEntityManager();
EntityTransaction et = em.getTransaction();
et.begin();
Address ad1 = new Address();
ad1.setStreet("Skaraktki");
ad1.setCode("64-340");
em.persist(ad1);
em.flush();
System.out.println(ad1.getAId());
et.commit();
}
}
Address class
#Entity
#Table(name = "ADDRESS")
#NamedQueries({
#NamedQuery(name = "Address.findAll", query = "SELECT a FROM Address a"),
#NamedQuery(name = "Address.findByAId", query = "SELECT a FROM Address a WHERE a.aId = :aId"),
#NamedQuery(name = "Address.findByStreet", query = "SELECT a FROM Address a WHERE a.street = :street"),
#NamedQuery(name = "Address.findByCode", query = "SELECT a FROM Address a WHERE a.code = :code")})
public class Address implements Serializable {
private static final long serialVersionUID = 1L;
// #Max(value=?) #Min(value=?)//if you know range of your decimal fields consider using these annotations to enforce field validation
#Id
#Basic(optional = false)
#Column(name = "A_ID")
private BigDecimal aId;
#Basic(optional = false)
#Column(name = "STREET")
private String street;
#Basic(optional = false)
#Column(name = "CODE")
private String code;
#OneToOne(cascade = CascadeType.ALL, mappedBy = "aId")
private Employee employee;
#OneToOne(cascade = CascadeType.ALL, mappedBy = "aId")
private Department department;
public Address() {
}
public Address(BigDecimal aId) {
this.aId = aId;
}
public Address(BigDecimal aId, String street, String code) {
this.aId = aId;
this.street = street;
this.code = code;
}
public BigDecimal getAId() {
return aId;
}
public void setAId(BigDecimal aId) {
this.aId = aId;
}
public String getStreet() {
return street;
}
public void setStreet(String street) {
this.street = street;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public Employee getEmployee() {
return employee;
}
public void setEmployee(Employee employee) {
this.employee = employee;
}
public Department getDepartment() {
return department;
}
public void setDepartment(Department department) {
this.department = department;
}
#Override
public int hashCode() {
int hash = 0;
hash += (aId != null ? aId.hashCode() : 0);
return hash;
}
#Override
public boolean equals(Object object) {
// TODO: Warning - this method won't work in the case the id fields are not set
if (!(object instanceof Address)) {
return false;
}
Address other = (Address) object;
if ((this.aId == null && other.aId != null) || (this.aId != null && !this.aId.equals(other.aId))) {
return false;
}
return true;
}
#Override
public String toString() {
return "jpaintroduction.Address[ aId=" + aId + " ]";
}
}
This happend to me because I manually added an entry to my database with the id 0 (zero).
In my case EclipseLink "couldn't" handle an id with zero.
So I added following to my persistence.xml:
<property name="eclipselink.allow-zero-id" value="true"/>
This property says EclipseLink to handle zero as a valid id.
[1] http://meetrohan.blogspot.de/2011/11/eclipselink-null-primary-key.html
You need to annotate your id field with #GeneratedValue, in order for JPA to know that the DB will generate the id automatically:
#Id
#Basic(optional = false)
#Column(name = "A_ID")
#SequenceGenerator( name = "mySeq", sequenceName = "MY_SEQ", allocationSize = 1, initialValue = 1 )
#GeneratedValue(strategy=GenerationType.IDENTITY, generator="mySeq")
private BigDecimal aId;
With oracle you can use GenerationType.IDENTITY and #SequenceGenerator in which case you don't need a trigger to query the sequence and populate the ID, JPA will do it for you. I'm not sure if GenerationType.AUTO will work with oracle but if it does, you'd need a trigger to query the sequence and populate the id. GenerationType.TABLE is the most portable solution, since you use an independent table managed by JPA to store the sequence, it works across all databases.
Check the docs in the link above.
As you said, the problem lies in the fact that even when you commit a transaction, the Auto-increment function isn't invoked right away. Actually, while you are managing database interactions with the same EntityManager it won't.
My question would rather be: why are you persisting, then committing the transaction if you flush it anyway? Looks like duplicated code to me.
See this on flush.

Categories

Resources