Problem when testing via #DataJpaTest with composite key entities - spring-data-jpa

I'm trying to test an entity that I've mapped with a composite key.
Simply, I tried to test from the Repository layer, but the desired data and results did not come out.
I don't use #CreationTimestamp very often, so you don't have to mention this part, but if this part is a problem,
please mention it.
Please take into account typos. Thanks.
The code looks like this:
#Entity
#NoArgsConstructor(access = AccessLevel.PROTECTED)
#EqualsAndHashCode
#Getter
#IdClass(TestId.class)
#ToString
public class TestEntity {
#Id
private Long groupCode;
#Id
private Long code;
#CreationTimestamp
#Column(nullable = false, updatable = false)
private LocalDateTime regDate;
#UpdateTimestamp
#Column(insertable = false)
private LocalDateTime modDate;
public TestEntity(Long groupCode, Long code) {
this.groupCode = groupCode;
this.code = code;
}
}
Repository
public interface TestEnttiyRepository extends JpaRepository<TestEntity, TestId> {
}
Composite-id Class
#NoArgsConstructor(access = AccessLevel.PROTECTED)
#EqualsAndHashCode
public class TestId implements Serializable {
private Long groupCode;
private Long code;
public TestId(Long groupCode, Long code) {
this.groupCode = groupCode;
this.code = code;
}
}
#DataJpaTest
class TestEnttiyRepositoryTest {
#Autowired
private TestEnttiyRepository testEnttiyRepository;
#Commit
#Test
void test() {
TestEntity testEntity = new TestEntity(1L, 1L);
TestEntity save = testEnttiyRepository.save(testEntity);
System.out.println("save = " + save);
}
}
Test Result
Hibernate:
select
testentity0_.code as code1_5_0_,
testentity0_.group_code as group_co2_5_0_,
testentity0_.mod_date as mod_date3_5_0_,
testentity0_.reg_date as reg_date4_5_0_
from
test_entity testentity0_
where
testentity0_.code=?
and testentity0_.group_code=?
save = TestEntity(groupCode=1, code=1, regDate=null, modDate=null)
Hibernate:
insert
into
test_entity
(reg_date, code, group_code)
values
(?, ?, ?)
Currently my question here is this.
The order of the output value through System.out.println and the insert query through save is reversed.
The LocalDateTime was not added automatically.

The order of the output value through System.out.println and the insert query through save is reversed.
Repository.save just as EntityManager.persist don't save anything.
They just make entities managed which will result in them being saved when a flush happens, which is at the end of the transaction, which in turn is at the end of your method, so after the System.out statement.

Related

How to append where clause to all queries that run with spring data MongoRepository?

I have entities that are persisted in MongoDB and use spring data MongoRepository to fetch data. Now i want to apply filter to all queries that executed on the entites, so i decided to use hibernate filter, something like this:
#Entity
#QueryEntity
#Document(collection = "Opportunity")
#NoArgsConstructor
#AllArgsConstructor
#Getter
#Setter
#CompoundIndexes({
#CompoundIndex(name = "productGroup_userId_uniqueness", def = "{'productGroupCode' : 1, 'userId': 1}", unique = true)
})
#FilterDef(name = "defaultFilter",parameters = #ParamDef(name = "unitCode",type = "string"))
#Filter(name = "defaultFilter" , condition = " unitCode like :unitCode")
public class Opportunity {
#Id
#Indexed
private String id;
#Indexed
#Enumerated(EnumType.STRING)
private OpportunityStatus opportunityStatus = OpportunityStatus.OPEN;
private LeadType leadType;
#Indexed
private String userId;
#Indexed
private String productCode;
#Indexed
private String productGroupCode;
#Indexed
private Long actionId;
private String assigneeId;
#Transient
private List<AbstractCommand> commandHistory = new ArrayList<>();
#Transient
private Map<Long, Boolean> actionStatus = new HashMap<>();
private String unitCode;
}
and this is the repository class:
#Repository
public interface OpportunityRepository extends MongoRepository<Opportunity, String>, QuerydslPredicateExecutor<Opportunity> {
// this repository contains more than 20 methods
// and all of theme removed for question brevity
}
And I enabled hibernate filter on session with this way:
Session session = (entityManager).unwrap(Session.class);
session.enableFilter(filterName).setParameter("unitCode", this.getCurrentUserUnitCode());
Now, when I call OpportunityRepository.findAll(Predicate predicate, Pageable pageable) i expected to apply the defined filter on the entity, but it didn't work.
I think the reason is that MongoRepository hasn't any sense of hibernate #Filter and i should use another way to append where clause to all mongo queries that running on the Opportunity entity.

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);
//...
}

Return more data than model contains using Spring Data

I'm working with Spring Data which is great stuff, but sometimes I need to get more data from database than my model can handle. For example I have model like below.
#Entity
#Table(name = "email")
public class Mail implements Serializable {
#Getter
#Setter
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Id
private Long id;
#Getter
#Setter
private String text;
}
An I my query will be more complex than usual. I want to get my model and in addition number of similar entities, using group by.
#Query(value = "SELECT m, COUNT(m) as countValue FROM Mail m GROUP BY m.text")
List<Mail> findAllNewsletters();
How I should handle something like that? My model does't contain countValue so I will get List<Object[]>
How to deal with that situation, keep my code clean, easiness
of using this.
Step 1: Create a container class to hold the output from your query.
class MailOccurence {
private final Mail mail;
private final Long recurrence;
public MailOccurence(final Mail mail, final Long recurrence) {
this.mail = mail;
this.recurrence = recurrence;
}
public Mail getMail() { return mail; }
public Long getRecurrence() { return recurrence; }
}
Step 2: Populate and return instances of the container class from the query.
Query(value = "SELECT new MailOccurence(m, COUNT(m)) FROM Mail m GROUP BY m.text")
List<MailGroup> findAllNewsletters();
For full details, see the JPA specification.
You can go for a DTO like following
public class MailEntry {
private Long id;
private String text;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
}
and inside your business logic you can take the advantage of spring template something like following
#Autowired
JdbcTemplate jdbcTemplate;
private static final String SQL = "SELECT m, COUNT(m) as countValue FROM Mail m GROUP BY m.text";
public List<MailEntry> getMailEntries() {
List<MailEntry> mailEntryList = jdbcTemplate.query(SQL, new RowMapper<MailEntry>() {
public MailEntry mapRow(ResultSet rs, int rowNum) throws SQLException {
MailEntry mailEntry = new MailEntry();
mailEntry.setId(rs.getInt(1));
mailEntry.setText(rs.getString(2));
return mailEntry;
}
});
return mailEntryList;
}
Hope this help.

EntityExistsException When Attempting Bulk Delete With OpenJPA

I have three classes: Location, MTFCC, and BorderPoint.
Location has a unidirectional #ManyToOne relationship with MTFCC, which is intended only as a Lookup table. No cascading is defined.
Location also has a bidirectional #ManyToOne/#OneToMany with BorderPoint. Since I want all associated BorderPoint objects to delete when I delete a Location, I set cascadetype.ALL on the Location side of the relationship.
Unfortunately, an EntityExistsException is being thrown when I attempt to delete a location:
org.apache.openjpa.persistence.EntityExistsException: Cannot delete or update
a parent row: a foreign key constraint fails (`mapmaker`.`BORDERPOINT`,
CONSTRAINT `BORDERPOINT_ibfk_1` FOREIGN KEY (`LOCATIONID`) REFERENCES `LOCATION`
(`LOCATIONID`)) {prepstmnt 21576566 DELETE t0, t1 FROM LOCATION t0 INNER JOIN
MTFCC t1 ON t0.MTFCCID = t1.MTFCCID WHERE (t0.STATEFP = ? AND t1.MTFCCCODE = ?)
[params=?, ?]} [code=1451, state=23000]
[ERROR] FailedObject: DELETE t0, t1 FROM LOCATION t0 INNER JOIN MTFCC t1 ON
t0.MTFCCID = t1.MTFCCID WHERE (t0.STATEFP = ? AND t1.MTFCCCODE = ?)
[java.lang.String]
It looks like it's attempting to delete the associated MTFCC object which I do NOT want to happen. I do, however, want the associated BorderPoint objects to be deleted.
Here is the code (chopped down a bit):
#SuppressWarnings("unused")
#Entity
#Table(name="LOCATION")
#DetachedState(enabled=true)
public class Location implements Serializable, IsSerializable, Cloneable {
private Long id;
private String stateGeoId;
private MTFCC mtfcc;
private List<BorderPoint> borderPointList;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name="LOCATIONID")
public Long getId() {
return id;
}
#ManyToOne
#JoinColumn(name="MTFCCID")
public MTFCC getMtfcc() {
return mtfcc;
}
#OneToMany(cascade = CascadeType.ALL, mappedBy = "location", fetch = FetchType.EAGER)
public List<BorderPoint> getBorderPointList() {
return borderPointList;
}
}
#Entity
#Table(name = "BORDERPOINT")
#DetachedState(enabled = true)
public class BorderPoint implements Serializable, IsSerializable {
private Long id;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name="BORDERID")
public Long getId() {
return id;
}
#ManyToOne(targetEntity = Location.class)
#JoinColumn(name="LOCATIONID")
public Location getLocation() {
return location;
}
}
#Entity
#Table(name = "MTFCC")
public class MTFCC implements Serializable, IsSerializable {
private Long id;
private String mtfccCode;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "MTFCCID")
public Long getId() {
return id;
}
// etc
}
And, for good measure, here is the deletion code:
#Override
#Transactional
public int removeByStateGeoIdAndMtfcc(String stateGeoId, String mtfccCode) throws RepositoryException {
EntityManager em = entityManagerFactory.createEntityManager();
String jpaQuery = "DELETE FROM Location L where L.stateFP = ?1 AND L.mtfcc.mtfccCode = ?2";
int affectedRows = 0;
Query query = em.createQuery(jpaQuery).setParameter(1, stateGeoId).setParameter(2, mtfccCode);
try {
em.getTransaction().begin();
affectedRows = query.executeUpdate();
em.getTransaction().commit();
} catch (Exception e) {
//log.debug("Exception: ", e);
throw new RepositoryException(e);
}
em.close();
return affectedRows;
}
Hopefully I copied all relevant parts... can anyone assist?
You aren't reading the error message correctly. It says that the deletion is forbidden because of the foreign key constraint between BorderPoint and Location.
The cascade delete would work if you used em.remove(location) to delete your Location. Using a delete query like you're doing won't automagically delete the BorderPoints before deleting the location.
Either load them and remove them using em.remove, or execute other delete queries before to delete the BorderPoints.

Why am I getting "Duplicate entry" errors for related objects upon merge in eclipselink?

I have an entity class that contains a map of key-value pairs which live in a different table and there may be no such pairs for a given entity. The relevant code for the entity classes is below.
Now, when I insert such an entity with persist(), then add key-value pairs, and then save it with merge(), I get duplicate entry errors for the related table that stores the key-value pairs. I tried to hold back insertion until the keys were added, to have one call to persist() only. This led to duplicate entry errors containing an empty (zero) id in the foreign key column (ixSource).
I followed the process in the debugger, and found that eclipselink seems to be confused about the cascading. While it is updating the entity, it executes calls that update the related table. Nonetheless, it also adds those operations to a queue that is processed afterwards, which is when the duplicate entry errors occur. I have tried CascadeType.ALL and MERGE, with no difference.
I'm using static weaving, if it matters.
Here's the entities`code, shortened for brevity:
#Entity
#Inheritance(strategy = InheritanceType.JOINED)
#DiscriminatorColumn(name = "sType")
#Table(name = "BaseEntity")
public abstract class BaseEntity extends AbstractModel
{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ix")
private long _ix;
}
#Entity
#Table(name = "Source")
public class Source extends BaseEntity
{
#OneToMany(cascade = CascadeType.MERGE)
#JoinTable(name = "SourceProperty", joinColumns = { #JoinColumn(name = "ixSource") })
#MapKey(name = "sKey")
private Map<String, SourceProperty> _mpKeys;
// ... there's more columns that probably don't matter ...
}
#Entity
#Table(name = "SourceProperty")
#IdClass(SourcePropertyKey.class)
public class SourceProperty
{
#Id
#Column(name = "sKey", nullable = false)
public String sKey;
#Id
#Column(name = "ixSource", nullable = false)
public long ixSource;
#Column(name = "sValue", nullable = true)
public String sValue;
}
public class SourcePropertyKey implements Serializable
{
private final static long serialVersionUID = 1L;
public String sKey;
public long ixSource;
#Override
public boolean equals(Object obj)
{
if (obj instanceof SourcePropertyKey) {
return this.sKey.equals(((SourcePropertyKey) obj).sKey)
&& this.ixSource == ((SourcePropertyKey) obj).ixSource;
} else {
return false;
}
}
}
I can't see how those errors would occur. Could you include the SQL and ful exception.
What version of EclipseLink are you using, did you try the latest release?
Why are you calling merge? Are you detaching the objects through serialization, if it is the same object, you do not need to call merge.
It could be an issue with the #MapKey, does it work if you remove this?