Spring data jpa inheritance - table per class not working - spring

I have an abstract entity.
#Entity
#Inheritance(strategy=InheritanceType.TABLE_PER_CLASS)
#EntityListeners(AuditingEntityListener.class)
public abstract class AbstractEntity {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
protected long id;
#CreatedBy
protected String createdBy;
#CreatedDate
protected Date creationDate;
#LastModifiedBy
protected String modifiedBy;
#LastModifiedDate
protected Date lastModifiedDate;
}
And 2 concrete implementations of this class:
Class A:
#Entity
#Table(name = "A")
public class A extends AbstractEntity {
#Column(name = "NAME", nullable = false)
private String name;
#Column(name = "PRIORITY", nullable = false)
private int priority;
}
Class B:
#Entity
#Table(name = "B")
public class B extends AbstractEntity {
#Column(name = "PLACE", nullable = false)
private String place;
#Column(name = "DISTANCE", nullable = false)
private int distance;
}
And a common repository interface:
#NoRepositoryBean
public interface IRepository extends Repository<AbstractEntity, Long> {
/**
* Method to query by unique id/PK.
* #param id
* #return Entity with id "id"
*/
#Query("select entity from #{#entityName} as entity where entity.id = ?1")
public AbstractEntity findById(long id);
/**
* Insert method
* #param abstractEntity
* #return modified entity after insertion
*/
public AbstractEntity save(AbstractEntity abstractEntity);
/**
* select all records from the table
* #return list of all entities representing records in the table
*/
#Query("select entity from #{#entityName} as entity")
public List<AbstractEntity> findAll();
/**
* delete record by id
* #param id
*/
public void deleteById(long id);
}
And each class has it's own repository which extends the generic repository:
public interface ARepository extends IRepository {
}
public interface BRepository extends IRepository {
}
When I invoke findAll() on ARespository, I get the records in both ARepository and BRepository. Since, the inheritance type is specified as TABLE_PER_CLASS, I assumed that a findAll() would only pick records from that table. I even added a query to the findAll() method to detect entity type and pick records appropriately, but this doesn't seem to be doing anything. Is there something I'm missing here?
I'm using Hibernate as my underlying persistence framework and am working on HSQLDB.
Thanks,
Aarthi

The typing of your repositories is incorrect change it to.
#NoRepositoryBean
public interface IRepository<Entity extends AbstractEntity> extends Repository<Entity, Long> {
}
public interface ARepository extends IRepository<A> {
}
public interface BRepository extends IRepository<B> {
}

Related

How to merge two existing nodes in neo4j using Spring, OGM and #RelationshipEntity

I am using Spring neo4j ogm and I have Entities already saved in database. Now I want to create new relationship between them with spring ogm. Problem is that i have only entities uuid in this moment, and I want to escape getEntityByUuid() which could get me entity object and then repo.save() would do the trick.
If i need to create custom query, can it be in repository in this format:
public interface EntityRepository extends Neo4jRepository<Entity, Long> {
#Query("MATCH (e:Entity {uuid:$0}), (e2:Entity{uuid:$1}) CREATE (e)-[:MENTION{relationshipProperties...}]->(e2)")
boolean createRelationshipBetweenExistingEntities(String entity1uuid, String entity2uuid, RelationshipNeo4j rel);
These are my classes:
public abstract class AbstractEntity {
#Id
#GeneratedValue
private Long id;
}
#RelationshipEntity(type = "MENTION")
public class RelationshipNeo4j extends AbstractEntity {
#Property
protected String type;
#Property
protected LocalDate date;
#StartNode
protected Entity start;
#EndNode
protected Entity end;
}
#NodeEntity
public class Entity extends AbstractEntity {
protected String name;
#Index(unique = true)
protected String uuid;
protected String wikiURL;
protected String description;
#Relationship(type="MENTION")
protected List<RelationshipNeo4j> relationships;
}
This is the closest I came:
#Query("MATCH (e:Entity {uuid:{entity1uuid}}), (e2:Entity{uuid:{entity2uuid}}) CREATE (e)-[r:MENTION{uuid:{relationshipUuid},type:{type},date:{date}}]->(e2) RETURN e,e2,r")
RelationshipNeo4j createRelationshipBetweenExistingEntities(String entity1uuid, String entity2uuid, String relationshipUuid, String type, String date);
We can't inset into query non primitive types:
https://markhneedham.com/blog/2017/12/01/neo4j-cypher-property-values-can-primitive-types-arrays-thereof/

JPA CrudRepository save() not populating primary key after save

I have very strange issue here. I am using composite primary key with #IdClass in my entities. It is working fine in every case, except save. After saving the entity, JPA is not firing SELECT query to select inserted data, and not merging the result. Though data is getting saved in database successfully. Also there are no errors. Below is some of the code which can help in debugging the issue:
AbstractEntity.java
#MappedSuperclass
#IdClass(PrimaryKey.class)
public abstract class AbstractEntity implements Serializable {
/** The Constant serialVersionUID. */
private static final long serialVersionUID = -1191422925622832672L;
/** The id. */
private String id;
...
/**
* Gets the id.
*
* #return the id
*/
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
public String getId() {
return id;
}
...
}
PrimaryKey.java
public class PrimaryKey implements Serializable {
/** The id. */
private String id;
/**
* Gets the id.
*
* #return the id
*/
#Column(name = "id")
#Convert(converter = CryptoConverter.class)
public String getId() {
return id;
}
...
}
User.java
#Entity
#Table(name = "user")
public class User extends AbstractEntity {
...
}
UserRepository.java
#Repository
public interface UserRepository extends CrudRepository<User, PrimaryKey> {
}
I have BigInt autoIncrement Id in database as primary key. But I want to expose it in encrypted form to outside world, so I have used #Converter to encrypt and decrypt it.
When I invoke userRepository.save(userEntity) from UserService, it persists the data, but does not return generated id from database.
How can I resolve this issue?
EDIT:
I have hosted demo project with this functionality here.
Since I am not seeing anywhere in your code, you need to specify Id, the strategy type and the column on the database.
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "applicant_id")

Entity mapping to DTO

I want to map query result to DTO of the following JPQL:
#Repository
public interface FooRepository extends JpaRepository<Foo, Id> {
#Query("select f.game, sum(f.timeSpent) as duration from foo f group by f.game order by duration desc")
List<Foo> findMostPlayable();
}
As a result, I receive list of objects which consist of GameCatalog object and Long number:
0 = {Object[2]#10670}
0 = {GameCatalog#10675}
1 = {Long#10676} 8968
Foo.class looks like:
#Entity
#Getter
#Setter
public class Foo{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#ManyToOne(optional = false)
#JoinColumn(name = "game_catalog_id", nullable = false)
private GameCatalog game;
private Long timeSpent;
}
I plan to use MapStruct to map model with DTO but I cannot do that since 'findMostPlayable' returns result in such a way above.
How I can implement mapping here?
And should I use JPA JPQL way or hibernate features like projections and so on?
To get result of query with aggregate function,you can create your own class instead of using entity and use that.
Ex.
public class MyFoo {
private String game;
private int duration;
public String getGame() {
return game;
}
public void setGame(String game) {
this.game = game;
}
public int getDuration() {
return duration;
}
public void setDuration(int duration) {
this.duration = duration;
}
}
#Repository
public interface FooRepository extends JpaRepository<Foo, Id> {
#Query("select new MyFoo(f.game as game, sum(f.timeSpent) as duration ) from foo f group by f.game order by duration desc")
List<MyFoo> findMostPlayable();
}

How to join multiple queryDSL tables

I have some tables and I want to get result using queryDSL join, but haven't found any examples on multiple joins using queryDSL.
I have these tables:
Account table: accountId (PK) | email | password
account_profile table: accountId (PK)(fk to account) | nickname
Community table: articleId (PK) | accountId (fk to account) | title | content
Now I want below JPQL to be queryDSL code
select r from community r join r.account.profile a where a.nickname = :nickname
I have entity metamodels - QAccount, QAccountProfile, QCommunity
Additionally, I have to get the result with pagination, so the query should be called with pageable object.
Here is my work that doesn't work yet.
JPAQuery</*What generic type expected?*/> query = new JPAQuery</*???*/>(entityManager);
Predicate predicate = query.from(QCommunity.community).join(/*join directly accountProfile? or account? is it QEntity or real entity?*/);
// where should I place nickname matching condition ?
...
list = (repository.findAll(predicate, pageable)).getContent();
Where should I place the nickname matching condition?
EDIT: Appended entity information
Account.java
#Entity
#Table(name="account", uniqueConstraints={
#UniqueConstraint(columnNames="account_seq"),
#UniqueConstraint(columnNames="email")
})
#DynamicInsert
#DynamicUpdate
#Data
#EqualsAndHashCode
#ToString(includeFieldNames=true)
#RequiredArgsConstructor(staticName="of")
#NoArgsConstructor
public class Account implements Serializable{
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name="account_seq", nullable=false, unique=true)
private Integer accountId;
#Column(name="email", nullable=false, unique=true)
#NonNull
private String email;
#NonNull
private String password;
#OneToOne(cascade=CascadeType.ALL, mappedBy="account")
private AccountProfile profile;
#OneToOne(cascade=CascadeType.ALL, mappedBy="account")
private AccountSecurity security;
}
AccountProfile.java
#Entity
#Table(name="account_profile", uniqueConstraints={
#UniqueConstraint(columnNames={"account_seq"}),
#UniqueConstraint(columnNames={"nickname"})
})
#DynamicInsert
#DynamicUpdate
#Data
#EqualsAndHashCode
#ToString(includeFieldNames=true)
#RequiredArgsConstructor(staticName="of")
#NoArgsConstructor
public class AccountProfile implements Serializable{
private static final long serialVersionUID = 1L;
#Id
#OneToOne(cascade=CascadeType.ALL)
#JoinColumn(name="account_seq", referencedColumnName="account_seq")
private Account account;
#Column(name="nickname", nullable=false)
#NonNull
private String nickname;
}
Community.java
#Entity
#Table(name="community", uniqueConstraints = {
#UniqueConstraint(columnNames="article_seq")
})
#DynamicInsert
#DynamicUpdate
#Data
#NoArgsConstructor
#EqualsAndHashCode
#ToString(includeFieldNames=true)
public class Community {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name="article_seq", nullable=false, unique=true)
private Long articleId;
#ManyToOne(cascade=CascadeType.ALL)
#JoinColumn(name="account_seq", referencedColumnName="account_seq")
private Account account;
#Column(name="title", nullable=false)
private String title;
#Column(name="content", nullable=false)
private String content;
#Temporal(TemporalType.TIMESTAMP)
#Column(name="reg_dt")
private Date date;
#Column(name="read_cnt", nullable=false)
private int readCount;
#Column(name="attach_url")
private String attachUrl;
#Column(name="attach_filename")
private String attachFileName;
#OneToMany(cascade=CascadeType.ALL, mappedBy="article")
private Set<CommunityReply> replies;
}
EDIT: PROBLEM SOLVED
To help others who is facing the problem like me, I am gonna post my working code. the code is searching any community articles with matching specific nickname.
#PersistenceContext
private EntityManager entityManager;
private List<Community> getList(int pageNo, String keyword, int rowsOnPage){
int offset = (pageNo -1) * rowsOnPage;
int limit = rowsOnPage;
JPAQuery<Community> query = new JPAQuery<Community>(entityManager);
QCommunity qCommunity = QCommunity.community;
QAccount qAccount = QAccount.account;
QAccountProfile qAccountProfile = QAccountProfile.accountProfile;
return query
.from(qCommunity)
.innerJoin(qCommunity.account ,qAccount)
.innerJoin(qAccount.profile, qAccountProfile)
.where(qAccountProfile.nickname.like("%"+keyword+"%"))
.orderBy(qCommunity.articleId.desc())
.offset(offset)
.limit(limit)
.fetch();
}
First of all, declare a custom extended base repository class for QueryDSL queries.
First the interface:
#NoRepositoryBean
public interface ExtendedQueryDslJpaRepository<T, ID extends Serializable>
extends JpaRepository<T, ID>, QueryDslPredicateExecutor<T> {
<T1> Page<T1> findAll(JPQLQuery jpqlQuery, Pageable pageable);
}
And then the implementation:
public class ExtendedQueryDslJpaRepositoryImpl<T, ID extends Serializable>
extends QueryDslJpaRepository<T, ID> implements ExtendedQueryDslJpaRepository<T, ID> {
private static final EntityPathResolver DEFAULT_ENTITY_PATH_RESOLVER = SimpleEntityPathResolver.INSTANCE;
private final EntityPath<T> path;
private final PathBuilder<T> builder;
private final Querydsl querydsl;
private EntityManager entityManager;
public ExtendedQueryDslJpaRepositoryImpl(JpaEntityInformation<T, ID> entityInformation, EntityManager entityManager) {
this(entityInformation, entityManager, DEFAULT_ENTITY_PATH_RESOLVER);
}
public ExtendedQueryDslJpaRepositoryImpl(JpaEntityInformation<T, ID> entityInformation,
EntityManager entityManager, EntityPathResolver resolver) {
super(entityInformation, entityManager);
this.path = resolver.createPath(entityInformation.getJavaType());
this.builder = new PathBuilder(this.path.getType(), this.path.getMetadata());
this.querydsl = new Querydsl(entityManager, this.builder);
this.entityManager = entityManager;
}
#Override
public <T1> Page<T1> findAll(JPQLQuery jpqlQuery, Pageable pageable) {
// Count query
final JPQLQuery<?> countQuery = jpqlQuery;
// Apply pagination
JPQLQuery<T1> query = querydsl.applyPagination(pageable, jpqlQuery);
// Run query
return PageableExecutionUtils.getPage(query.fetch(), pageable, countQuery::fetchCount);
}
}
Define the new class as base for base and repositories in a #Configuration class.
#Configuration
#EnableJpaRepositories(basePackageClasses = ..., repositoryBaseClass = ExtendedQueryDslJpaRepositoryImpl.class)
Your repositories then should extend from the new interface (which of course extends JpaRepository):
#Repository
public interface CommunityRepository extends ExtendedQueryDslJpaRepository<Community, Long> {
}
Then, you can try the following code:
String nickname = "nick";
QAccount account = QAccount.account;
QAccountProfile accountProfile = QAccountProfile.accountProfile;
QCommunity community = QCommunity.community;
JPQLQuery query = new JPAQuery(entityManager);
BooleanBuilder predicate = new BooleanBuilder();
predicate.and(accountProfile.nickname.eq(nickname));
// select r from community r join r.account.profile a where a.nickname = :nickname
query.from(community)
.join(community.account, account)
.join(account.accountProfile, accountProfile)
.where(predicate);
repository.findAll(query, pageable);
Hope that helps.
I found one solution as
QEntity qEntity1 = new QEntity("qEntity1");
QEntity qEntity2 = new QEntity("qEntity2");
so while querying you can use
new JPAQueryFactory(entityManager).from(qSampleBO)
.innerJoin(qEntity1).on(qEntity1.id.eq(qSampleBO.address.id))
.innerJoin(qEntity2).on(qEntity2.id.eq(qSampleBO.secondary_address.id))
...

Spring Data JPA remove child entities

I have a load repository.
#Transactional
public interface MyLoadRepository extends CrudRepository<ParentEntity, Serializable> {
}
Then is my ParentEntity.
#MappedSuperclass
public class ParentEntity {
#Id
#GeneratedValue(generator = "system-uuid")
#GenericGenerator(name = "system-uuid", strategy = "uuid")
#Column(name = "id", unique = true)
private String uuid;
public String getUuid() {
return uuid;
}
public void setUuid(String uuid) {
this.uuid = uuid;
}
}
Then I have multiple child entities.
#Entity
#Table(name = "EntityA")
public class EntityA extends ParentEntity {
}
#Entity
#Table(name = "EntityB")
public class EntityB extends ParentEntity {
}
Ques : I want to delete these entities separately by my repository.
If I write something like this.
#Autowired
private MyLoadRepository repository;
and then repository.deleteAll()
I get error that repository is not entity (It obiviously not).
Here I want to delete either entityA or entityB data completely based on some condition. How can I do that ?
We should create repository per entity and not on non entity classes.
So, for your case you need 2 repository classes
#Transactional
public interface EntityARepo extends CrudRepository< EntityA, String> {
}
#Transactional
public interface EntityBRepo extends CrudRepository< EntityB, String> {
}
now in service classes you can do
#Autowired
private EntityARepo repoA;
#Autowired
private EntityBRepo repoB;
and then you can call delete method based on your condition
repoA.deleteAll()
or
repoB.deleteAll()
You need to fetch the entity based on a condition. For example, if the EntityA has a primary key uuid, then you must find EntityA by uuid and then delete the EntityA.
EntityA entityA = entityARepo.findOne(uuid);
repository.delete(entityA);
EntityB entityB = entityBRepo.findOne(uuid);
repository.delete(entityB);

Resources