Spring Data JPA - java.sql.Timestamp is getting null for rest of the list or set of entity - spring-data-jpa

Consider that i have a entity.
Class Employee {
#Id
private integer id;
private String name;
private Timestamp effectiveFrom;
}
and i have a list of value to it..
List<Employee> Employees = new ArrayList<>();
[1,"Employee1", "2019-10-10 00:00:00.000"]
[1,"Employee2", null]
[1,"Employee3", "2019-10-10 00:00:00.000"]
[1,"Employee4", "2019-10-10 00:00:00.000"]
When i do - repository.saveAll(Employees);
The first and second employee are saved correctly from third employee on wards the effectiveFrom column (Timestamp) - is getting null..
Is it expected behavior by Spring Data JPA ?

First you should not use the same id for any object. I would suggest to add the #GeneratedValue annotation for the primary key. I have create the entity with Lombok like that:
#Entity
#Data
#NoArgsConstructor
class Employee {
#Id
#GeneratedValue
private Integer id;
private String name;
private Timestamp effectiveFrom;
public Employee(String name, String effectiveFrom) {
this.name = name;
this.effectiveFrom = effectiveFrom == null ? null : Timestamp.valueOf(effectiveFrom);
}
}
At the end I create a simple jpa repository and save all entities:
#Bean
CommandLineRunner run(EmployeeRepository employeeRepository) {
return args -> {
List<Employee> employees = List.of(new Employee("Employee1", "2019-10-10 00:00:00.000"),
new Employee("Employee2", null), new Employee("Employee3", "2019-10-10 00:00:00.000"),
new Employee("Employee4", "2019-10-10 00:00:00.000"));
employeeRepository.saveAll(employees);
employeeRepository.findAll().forEach(System.out::println);
};
}
The console output looks like that:
Employee(id=1, name=Employee1, effectiveFrom=2019-10-10 00:00:00.0)
Employee(id=2, name=Employee2, effectiveFrom=null)
Employee(id=3, name=Employee3, effectiveFrom=2019-10-10 00:00:00.0)
Employee(id=4, name=Employee4, effectiveFrom=2019-10-10 00:00:00.0)
Please have a look at this short example. If you cannot find the error in your code, please post your code so I can have a look.

Related

JPARepository - delete using date comparison with derived query

I'm trying to use JPARepository in Spring Boot to delete records that are less than a certain date, for for a given userid
Should be something like this Delete * from [table] where expiration_date < [date] and userid = [userid]
I thought I should be able to use one of the automatically generated methods
int deleteByExpiryDateBeforeAndUser(Date date, User user);
But this is generating a Select and not a Delete. What am I doing wrong?
Update
Entity class
#Getter
#Setter
#ToString
#Entity(name = "refresh_token")
public class RefreshToken {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
#OneToOne
#JoinColumn(name = "user_id", referencedColumnName = "id")
private User user;
#Column(nullable = false, unique = true)
private String token;
#Column(nullable = false)
private Date expiryDate;
public RefreshToken() {
}
}
Repository class
#Repository
public interface RefreshTokenRepository extends JpaRepository<RefreshToken, Long> {
Optional<RefreshToken> findByToken(String token);
#Modifying
void deleteByUserIdAndExpiryDateBefore(Long userId, Date expiryDate);
int deleteByUser(User user);
}
Here's how I'm calling it
#Transactional
public void deleteExpiredTokens(User user) {
refreshTokenRepository.deleteByUserIdAndExpiryDateBefore(user.getId(), new Date());
}
You see a select statement because Spring Data first loads entities by condition.
Then once entities became 'managed' Spring Data issues a delete query for each entity that was found.
If you want to avoid redundant SQL query - you have to consider #Query annotation.
Then your code will look like this:
#Repository
public interface RefreshTokenRepository extends JpaRepository<RefreshToken, Long> {
// ...
#Query(value = "DELETE FROM refresh_token WHERE user_id =:userId AND expiry_date < :expiryDate", nativeQuery = true)
#Modifying
void deleteByUserIdAndExpiryDateBefore(Long userId, Date expiryDate);
//...
}

Spring JPA using primary key within #Embeddable class

In my application i have a scenario to fetch data from entity based on give input code and date.
The combination of code and date will be unique and will return a single record.
Below is my entity
class JpaEntity
{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private id ;
private String code;
private Date date;
private title;
//Getters and Setters
}
I have tried below approcah by changing the entity.
class JpaEntity
{
private String title;
//Getters and setters
#EmbededId
private EntityId entityID
#Embedable
public static class EntityId implements Serializable{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private id ;
private String code;
private Date date;
//Getters and Setters
}
}
I am using the entity to search based on code and date
public interface PersistentDAO extends JpaRepository<JpaEntity,String> {
{
#Query("SELECT cal FROM JpaCalendar cal" + " WHERE cal.calendarId.currencyCode=:currencyCode "
+ " AND cal.calendarId.date=:date")
Optional<JpaCalendar> findById(String currencyCode, Date date);
JpaEntity findByID(String code,Date date)
}
But the JPA is throwing error saying Component Id is not found.
is it mandatory all the field in #Embedable are primary?
is it possible #Embedable class (composite id) contain the both primary and non-primay keys.
Since i am not supposed to change the structure of the table is there any way to achieve following:
Fetch record based on give code and date.
Insert new record where in id the primary key should be auto incremented
Please suggest.
Use #Embedded & #Embeddable and don't use static class inside class
class JpaEntity
{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private id ;
#Embedded
private CodeDate codeDate;
private title;
}
#Embeddable
class CodeDate {
private String code;
private Date date;
}
You can use Jpa method naming query and create a CodeDate object call like this
public interface PersistentDAO extends JpaRepository<JpaEntity,String> {
{
JpaEntity findByCodeDate(CodeDate codeDate);
}

Crieria API query using criteriabuilder.construct with a non existing relationship

Given this very simple DTO:
#Entity
public class Employee implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
private String name;
#OneToOne
private Employee boss;
}
I'd like to make a query that gathers all employee names and their boss' id, put in a nice clean POJO:
public class EmployeeInfo {
private String name;
private Long bossId;
public EmployeeInfo(String name, Long bossId) {
this.name = name;
this.bossId = bossId;
}
}
This query should be of use:
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<EmployeeInfo> query = cb.createQuery(EmployeeInfo.class);
Root<Employee> root = query.from(Employee.class);
query.select(
cb.construct(EmployeeInfo.class,
root.get("name").as(String.class),
root.get("boss").get("id").as(Long.class)));
result = em.createQuery(query).getResultList();
When a bossId is present in the employee column this works just fine. But when no boss id is set the record will be completly ignored. So how do i treat this non existing boss relation as null or 0 for the construct/multiselect?
In pure SQL it is easy:
SELECT name, COALESCE(boss_id, 0) FROM EMPLOYEE;
But for the love of god i cannot make the criteria api do this.
cb.construct(EmployeeInfo.class,
root.get("name").as(String.class),
cb.coalesce(root.get("boss").get("id").as(Long.class), 0L)));
The problem is that root.get("boss") generate query with cross join like this from Employee employee, Employee boss where employee.boss.id=boss.id. So records where employee.boss.id is null are ignored.
To solve the problem you should use root.join("boss", JoinType.LEFT) instead of root.get("boss")

Eclipselink JPA - Update entity with #EmbeddedCollection and map does not insert new entry

I am looking at a very simple entity with a Map #ElementCollection.
The entity:
#Entity
#RequiredArgsConstructor #NoArgsConstructor #Data
public class Attribute {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#NonNull
private String name;
#ElementCollection
private Map<Integer, AttributeValue> attributes = new HashMap<>();
}
The Embeddable used as the map value:
#Embeddable
#RequiredArgsConstructor #NoArgsConstructor(access = PRIVATE) #Data
public class AttributeValue {
#NonNull
private String name;
}
When I try to update the entity with a new map entry where there is already an equal persisted value (with the same name in this case) the map entry is not inserted.
This test reproduces this issue:
#Test
public void should_persist_attribute_with_same_value() {
Attribute name = new Attribute("name");
name.getAttributes().put(1, new AttributeValue("1"));
name.getAttributes().put(2, new AttributeValue("2"));
name = attributeRepository.saveAndFlush(name);
name.getAttributes().put(3, new AttributeValue("2")); //this value is already in the map and is not inserted
name.getAttributes().put(4, new AttributeValue("4"));
name = attributeRepository.saveAndFlush(name);
entityManager.clear();
//the assertion fails and finds only three items (with key 1,2 and 4)
then(attributeRepository.findOne(name.getId()).getAttributes()).hasSize(4);
}
It seems that there is a bug in eclipselink that ignores map values that are already used with another map key in the map.
I also tried with hibernate - there the same code works as expected.
Any ideas?
I am using:
spring-boot 1-4-0.M2
spring-data-jpa 1.10.1.RELEASE
eclipselink 2.6.1

The type of field isn't supported by declared persistence strategy "OneToMany"

We are new to JPA and trying to setup a very simple one to many relationship where a pojo called Message can have a list of integer group id's defined by a join table called GROUP_ASSOC. Here is the DDL:
CREATE TABLE "APP"."MESSAGE" (
"MESSAGE_ID" INTEGER NOT NULL GENERATED ALWAYS AS IDENTITY (START WITH 1, INCREMENT BY 1)
);
ALTER TABLE "APP"."MESSAGE" ADD CONSTRAINT "MESSAGE_PK" PRIMARY KEY ("MESSAGE_ID");
CREATE TABLE "APP"."GROUP_ASSOC" (
"GROUP_ID" INTEGER NOT NULL,
"MESSAGE_ID" INTEGER NOT NULL
);
ALTER TABLE "APP"."GROUP_ASSOC" ADD CONSTRAINT "GROUP_ASSOC_PK" PRIMARY KEY ("MESSAGE_ID", "GROUP_ID");
ALTER TABLE "APP"."GROUP_ASSOC" ADD CONSTRAINT "GROUP_ASSOC_FK" FOREIGN KEY ("MESSAGE_ID")
REFERENCES "APP"."MESSAGE" ("MESSAGE_ID");
Here is the pojo:
#Entity
#Table(name = "MESSAGE")
public class Message {
#Id
#Column(name = "MESSAGE_ID")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int messageId;
#OneToMany(fetch=FetchType.LAZY, cascade=CascadeType.PERSIST)
private List groupIds;
public int getMessageId() {
return messageId;
}
public void setMessageId(int messageId) {
this.messageId = messageId;
}
public List getGroupIds() {
return groupIds;
}
public void setGroupIds(List groupIds) {
this.groupIds = groupIds;
}
}
I know this is wrong as there is no #Column mapping to GROUP_ASSOC.GROUP_ID for the groupIds property, but hopefully this illustrates what we are trying to do. When we run the following test code we get <openjpa-1.2.3-SNAPSHOT-r422266:907835 fatal user error> org.apache.openjpa.util.MetaDataException: The type of field "pojo.Message.groupIds" isn't supported by declared persistence strategy "OneToMany". Please choose a different strategy.
Message msg = new Message();
List groups = new ArrayList();
groups.add(101);
groups.add(102);
EntityManager em = Persistence.createEntityManagerFactory("TestDBWeb").createEntityManager();
em.getTransaction().begin();
em.persist(msg);
em.getTransaction().commit();
Help!
When you are working with JPA, you should think Object and relations between Objects and you should map your Object model, not ids, to your relational model (it is possible to map a List of basic values with #ElementCollection in JPA 2.0 though but what I said just before still applies).
Here, (assuming this really is a one-to-many relation between Message and GroupAssoc and not a many-to-many relation between Message and Group entities) you should have something like this:
#Entity
#Table(name = "MESSAGE")
public class Message implements Serializable {
#Id
#Column(name = "MESSAGE_ID")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long messageId;
#OneToMany(fetch=FetchType.LAZY, cascade=CascadeType.PERSIST)
private List<GroupAssoc> groupAssocs = new ArrayList<GroupAssoc>();
public Long getMessageId() {
return messageId;
}
public void setMessageId(Long messageId) {
this.messageId = messageId;
}
public List<GroupAssoc> getGroupAssocs() {
return groupAssocs;
}
public void setGroupAssocs(List<GroupAssoc> groupAssocs) {
this.groupAssocs = groupAssocs;
}
// equals() and hashCode()
}
And another entity for GroupAssoc.
PS: Your DDL really looks like a (M:N) relation between MESSAGE and GROUP (or I don't understand the PK constraint of GROUP_ASSOC) but you didn't show any FK constraint on GROUP_ID so I'm not 100% sure. But if that's the case, then you should use an #ManyToMany instead of #OneToMany.