DataJpaTest using H2 not able to access data in Subselect Entity - spring-boot

I am running a unit test set up as a DataJpaTest with an H2 database. It worked well, until I introduced a new Immutable Entity set using a Subselect. It does not break, but it does not find data that it should. I see in the SQL that it is correctly joining to the Subselect query.
When running as normal as an application, the #Subselect entity works fine, so it is an issue with the unit test setup somehow.
Here is my Main entity:
#Entity
#DynamicUpdate
#Getter
#Setter
#EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false)
#Table(name = "apps", schema = "public")
public class App extends BaseEntity
{
#Id
#Column(name = "app_id", nullable = false)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "ent_generator")
#SequenceGenerator(name = "ent_generator", sequenceName = "entity_seq", allocationSize = 1)
private long appId;
#EqualsAndHashCode.Include
#Basic
#Column(name = "app_name", length = 200)
private String appCode;
#EqualsAndHashCode.Include
#Basic
#Column(name = "app_ver_name", length = 200)
private String appVersion;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "parent_app_id")
private App parent;
// ...
#OneToMany(
mappedBy = "app",
cascade = CascadeType.ALL,
orphanRemoval = true
)
private Set<TrainingRequirement> trainingRequirements = new HashSet<>();
#OneToMany(mappedBy = "app")
private Set<EffectiveTrainingRequirement> effectiveTrainingRequirements = new HashSet<>();
// ...
}
Here is my #Subselect entity:
#Entity
#Immutable
#Subselect(
"SELECT\n" +
" tr.trn_req_id AS trn_req_id\n" +
" , tr.app_id AS app_id\n" +
" FROM apps a\n" +
" JOIN training_reqs tr\n" +
"-- ...")
#Synchronize({"apps","training_reqs"})
public class EffectiveTrainingRequirement
{
#Id
#Column(name = "trn_req_id", nullable = false)
private long trainingRequirementId;
#EqualsAndHashCode.Include
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "app_id")
private App app;
}
Here is the Unit test code (vastly reduced for relevant parts):
#RunWith(SpringRunner.class)
#DataJpaTest
public class TrainingRequirementRepositoryTest
{
#Autowired
private TestEntityManager entityManager;
#Autowired
private AppRepository appRepository;
#Before
public void setup()
{
// ...
app4 = new App(organization, "a4");
app4.addTrainingRequirement(new TrainingRequirement("treq6", c6));
app4.addTrainingRequirement(new TrainingRequirement("treq7", c7, tr1));
app4.addTrainingRequirement(new TrainingRequirement("treq8", c8, tr2));
entityManager.persist(app4);
app4v2 = new App(organization, "a4", app4, "v2");
app4v2.setParent(app4);
app4v2.addTrainingRequirement(treq7chg = new TrainingRequirement("treq7", c7, tr2));
treq7chg.setChangeType(InheritOverride.CHANGE);
app4v2.addTrainingRequirement(treq6rmv = new TrainingRequirement("treq6"));
treq6rmv.setChangeType(InheritOverride.REMOVE);
app4v2.addTrainingRequirement(treq9add = new TrainingRequirement("treq9", c9, tr4));
treq9add.setChangeType(InheritOverride.ADDITION);
entityManager.persist(app4v2);
}
#Test
public void test_AppWithEffectiveTR()
{
App app = appRepository.getAppWithParent(organization, "a4", "v2").get();
logger.debug("# tr: "+app.getTrainingRequirements().size());
logger.debug("# etr: "+app.getEffectiveTrainingRequirements().size());
for (EffectiveTrainingRequirement tr : app.getEffectiveTrainingRequirements())
logger.debug("tr: "+tr.toString());
}
}
The repository:
#Repository
public interface AppRepository extends CrudRepository<App, Long>
{
String APP_FETCH_QUERY = "SELECT a FROM App a " +
"LEFT JOIN FETCH a.parent p " +
"LEFT JOIN FETCH a.trainingRequirements atr " +
"LEFT JOIN FETCH a.effectiveTrainingRequirements ";
#Query(value = APP_FETCH_QUERY +
"WHERE a.organization = :org " +
" AND a.appCode = :appCode " +
" AND a.appVersion = :appVersion" )
Optional<App> getAppWithParent(#Param("org") Organization org,
#Param("appCode") String appCode,
#Param("appVersion") String appVersion);
}

Related

#OneToMany new Arraylist() is null

Hi I have a ManyToOne relationship between book and Student entity. I created this relationship as a bi-directional relationship. See classes below
Book class
#Getter
#Setter
#Builder
#AllArgsConstructor
#NoArgsConstructor
#Entity(name = "Book")
#Table(name = "book")
public class Book {
#Id
#SequenceGenerator(
name = "book_sequence",
sequenceName = "book_sequence",
allocationSize = 1
)
#GeneratedValue(
strategy = GenerationType.SEQUENCE,
generator = "book_sequence"
)
#Column(
name = "id",
updatable = false
)
private Long id;
#Column(
name = "book_name",
nullable = false
)
private String bookName;
#ManyToOne
#JoinColumn(
name = "student_id",
nullable = false,
referencedColumnName = "id",
foreignKey = #ForeignKey(
name = "student_book_fk"
)
)
private Student student;
}
Student class
#Getter
#Setter
#Builder
#AllArgsConstructor
#NoArgsConstructor
#Entity(name = "Student")
#Table(name = "student",
uniqueConstraints = {
#UniqueConstraint(name = "student_email_unique", columnNames = "email")
}
)
public class Student {
#Id
#SequenceGenerator(name = "student_seq", sequenceName = "student_seq", allocationSize = 1)
#GeneratedValue(strategy = GenerationType.SEQUENCE,generator = "student_seq")
#Column(name = "id",updatable = false)
private Long id;
#Column(name = "first_name", nullable = false, columnDefinition = "TEXT")
private String firstName;
#OneToMany(
mappedBy = "student",
orphanRemoval = true,
cascade = { CascadeType.PERSIST, CascadeType.REMOVE }
)
private List<Book> books = new ArrayList<>();
public void addBook(Book book){
// if(books == null){
// this.books = new ArrayList<>();
// }
if(!this.books.contains(book)){
this.books.add(book);
book.setStudent(this);
}
}
public void removeBook(Book book){
if(this.books.contains(book)){
this.books.remove(book);
book.setStudent(null);
}
}
#Override
public String toString() {
return "Student{" +
"id=" + id +
", firstName='" + firstName + '\'' +
", lastName='" + lastName + '\'' +
", email='" + email + '\'' +
", age=" + age +
", studentIdCard=" + studentIdCard.getCardNumber() +
'}';
}
}
When I run the application I am getting an error
Cannot invoke "java.util.List.contains(Object)" because "this.books" is null
This is available inside the addBook method in my student entity. In my student entity I initialized my books variable as below. I tried solutions provided in other similar issues raised in stackoverflow but unable to get this issue sorted.
#OneToMany(
mappedBy = "student",
orphanRemoval = true,
cascade = { CascadeType.PERSIST, CascadeType.REMOVE }
)
private List<Book> books = new ArrayList<>();
I am not sure whether use of lombok has something to do with this. But if I write my code inside addBook method as below, issue is sorted
public void addBook(Book book){
if(books == null){
this.books = new ArrayList<>();
}
if(!this.books.contains(book)){
this.books.add(book);
book.setStudent(this);
}
}
Any ideas what might cause this issue?
Cheers
I used the #Builder.Default annotation for the books variable.
#OneToMany(
mappedBy = "student",
orphanRemoval = true,
cascade = { CascadeType.PERSIST, CascadeType.REMOVE }
)
#Builder.Default
private List<Book> books = new ArrayList<>();
Reference - use Lombok #Builder #Default #Singular to initialise List<>

entity savedAndFlushed not found later in the same transaction

I have a loop where I persist in DB some new objects if they are not already existing. (productPrice.product)
But in my loop, it does not find a previously persisted entity (with save and flush), and the re-creation of the entity triggers an DataIntegrityViolationException because my unique constraint is violated (ERROR: duplicate key value violates unique constraint "uk_product_b2c_ext_id")
Here is my code (simplified). I don't understand why it doesn't work.
#Transactional(propagation = Propagation.REQUIRES_NEW)
public void myFunction(List<ProductPriceCatalogResponseAPI.ProductPrice> productPrices) {
for (ProductPriceCatalogResponseAPI.ProductPrice productPrice : productPrices) {
ProductPriceB2C pp = new ProductPriceB2C();
pp.setProduct(productB2CRepository.findByExtId(productPrice.getProduct().getId())
.orElse(createNewProductFromExtId(productPrice.getProduct())));
productPriceB2CRepository.saveAndFlush(pp)
}
}
private ProductB2C createNewProductFromExtId(ProductPriceCatalogResponseAPI.Product product) {
ProductB2C p = new ProductB2C();
p.setName(product.getLabel());
p.setExtId(product.getId());
log.info("Persist product {} with external id {}", product.getLabel(), product.getId());
return productB2CRepository.saveAndFlush(p); // Exception here
}
ps : there is no cascade at all between ProductPrice and Product
Here is the code of my entities :
#Entity
#Table(name = "product_b2c")
public class ProductB2C {
#Id
#SequenceGenerator(name = "product_b2c_generator", sequenceName = "product_b2c_id_seq", allocationSize = 1, initialValue = 10000)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "product_b2c_generator")
private Long id;
#Column(name = "ext_id")
private Long extId;
#Column(name = "name")
private String name;
// getters and setters
}
#Entity
#Table(name = "product_price_b2c")
public class ProductPriceB2C extends AbstractAuditingEntity {
#Id
#SequenceGenerator(name = "product_price_b2c_generator", sequenceName = "product_price_b2c_id_seq", allocationSize = 1, initialValue = 10000)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "product_price_b2c_generator")
private Long id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "product_id")
private ProductB2C product;
// other fields
// getters and setters
}

Left Join Fetch Behaving Like Inner Join

I have a one-to-many relationship between routes and stops. In order to maintain an audit trail, my Stop entities have a "historic" boolean.
When fetching a route, I want to ignore historic stops, and so I constructed this query:
#Query("select r from Route r " +
"left join fetch r.schedules schedule " +
"left join fetch r.stops stop " +
"where r.routeId = :routeId and stop.historic = false ")
Optional<Route> findByIdLoadStops(#Param("routeId") int routeId);
This works fine when the route has non-historic stops and no stops, but when the route only has a historic stop (which shouldn't happen but I want to be able to at least handle it), it returns an empty optional as though an inner join has been performed.
When logging the JPA query created by hibernate, I can see that the query uses a left outer join.
What have I done incorrectly?
Route and Stop entities:
#Table(name = "route")
#Entity
public class Route {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "routeId", columnDefinition = "SMALLINT(5) UNSIGNED")
private int routeId;
#Column(name = "locked")
private boolean locked = false;
#OneToMany(mappedBy = "route",
cascade = CascadeType.ALL,
fetch = FetchType.LAZY)
#OrderBy("stopTime asc")
private SortedSet<Stop> stops = new TreeSet<>();
public Route() {
}
}
#Table(name = "stop", uniqueConstraints = {
#UniqueConstraint(columnNames = {"stopTime", "routeId"}),
#UniqueConstraint(columnNames = {"stopName", "routeId"})})
#Entity
public class Stop implements Comparable<Stop> {
#Id
#Column(name = "stopId")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int stopId;
#Column(name = "routeId",
columnDefinition = "SMALLINT(5)",
updatable = false, insertable = false)
private int routeId;
#ManyToOne(cascade = CascadeType.MERGE,
fetch = FetchType.LAZY)
#JoinColumn(name = "routeId")
private Route route;
#Column(name = "stopTime")
private LocalTime stopTime;
#Column(name = "stopName")
private String stopName;
#JoinColumn(name = "originalId", referencedColumnName = "stopId")
#ManyToOne(fetch = FetchType.LAZY)
private Stop originalStop = this;
#Column(name = "historic")
private boolean historic = false;
public Stop() {
}
}

QuerySyntaxException in Spring Data JPA custom query

I have a Entity:
#Entity
#Table(name = "story", schema = "")
#Data
public class Story implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "sID", unique = true, nullable = false)
private Long sID;
#Column(name = "vnName", nullable = false)
private String vnName;
#Temporal(TemporalType.TIMESTAMP)
#DateTimeFormat(pattern = "dd-MM-yyyy HH:mm:ss")
#Column(name = "sUpdate", length = 19)
private Date sUpdate;
}
And:
#Entity
#Table(name = "chapter", schema = "")
#Data
public class Chapter implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "chID", unique = true, nullable = false)
private Long chID;
#Column(name = "chName", nullable = false)
private String chName;
#JoinColumn(name = "sID", referencedColumnName = "sID")
#ManyToOne(fetch = FetchType.LAZY)
private Story story;
}
I had created custom pojo to get the latest update story with the latest chapter:
#Data
public class NewStory{
private Story story;
private Chapter chapter;
}
but when I get list :
#Repository
public interface StoryRepository extends CrudRepository<Story, Long> {
#Query(value="SELECT NEW com.apt.truyenmvc.entity.NewStory(s as newstory, c as newchapter)"
+ " FROM story s LEFT JOIN (SELECT * FROM Chapter c INNER JOIN "
+ " (SELECT MAX(c.chID) AS chapterID FROM Story s LEFT JOIN Chapter c ON s.sID = c.sID GROUP BY s.sID) d"
+ " ON c.chID = d.chapterID) c ON s.sID = c.sID order by s.sUpdate desc")
public List<NewStory> getTopView();
}
Error:
Warning error: org.hibernate.hql.internal.ast.QuerySyntaxException: story is not mapped.
Who could help me fix it? Or could it be done in a different way?
The error is pretty self explainatory. And its just a typo in your query. You are using story. And obviously thats not mapped as an Entity.
Fix it to Story

Delete Operation on Embedded Object Spring JPA

I Have below Entities :
#Entity(name = "USRGRP_MAP")
public class UserGroupMapping {
#Id
#Column(name = "USRGRP_ID")
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "USER_GRP_MAP_SEQ")
#SequenceGenerator(sequenceName = "usrgrp_map_seq",allocationSize = 1,name = "USER_GRP_MAP_SEQ")
private Long mappingId;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "USER_ID", referencedColumnName = "USER_ID")
private User user;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "USR_GRP_ID", referencedColumnName = "USR_GRP_ID")
private UserGroup group;
#Column(name = "USR_USRGRP_ACT")
private String userGroupAct;
getter/setters
}
#Entity(name = "USER")
public class User {
#Id
#Column(name = "USER_ID")
private Long userId;
#Column(name = "LOGIN_ID")
private String userName;
getter/setters
}
#Entity(name = "USR_GRP")
public class UserGroup {
#Id
#Column(name = "USR_GRP_ID")
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "USER_GRP_SEQ")
#SequenceGenerator(sequenceName = "usr_grp_seq",allocationSize = 1,name = "USER_GRP_SEQ")
private long groupId;
#Column(name = "GRP_NM")
private String groupName;
#Column(name = "GRP_DESC")
private String groupDesc;
getter/setters
}
UserGroupMapping contains has many to one relationship with both user and group.
Now I want to do CRUD operation on UserGroupMapping for that I have created repository as below:
public interface UserGroupMappingRepository extends JpaRepository<UserGroupMapping, Long> {
List<UserGroupMapping> findByGroup(UserGroup group);
List<UserGroupMapping> findByUser(User user);
}
Now I want to write delete operation(for particular user and group) on UserGroupMapping without deleting any entry in USER and USR_GRP table , Just need to remove entry from USRGRP_MAP table.
I am trying to achieve it using native query:
#Query(value = "delete from USR_USRGRP_MAP where user_id = :userId and usr_grp_id = :groupId",nativeQuery = true)
void deleteUserGroupMappingByUserAndGroup(#Param("userId") Long userId, #Param("groupId") Long groupId);
Facing Exception Invalid SQL grammar, although query work fine in sql developer.
Below is my service class:
#Service
public class UserGroupMappingServiceImpl implements UserGroupMappingService{
#Autowired
private UserGroupMappingRepository repository;
#Override
public void deleteUserGroupMapping(Long userId, Long groupId) {
repository.deleteUserGroupMappingByUserAndGroup(userId,groupId);
}
}
Could anyone suggest correct way to delete entry from UserGroupMapping without deleting user and group ?
Below is USRGRP_MAP table:
USRGRP_ID USER_ID USR_USRGRP_ID USR_USRGRP_ACT
------------- ---------- ------------- -
41 306106 41 Y
14 108527 14 Y
8 295597 8 N
10 296518 10 Y
11 295597 11 Y
Thanks in advance .
Try to change
#Query(value = "delete from USR_USRGRP_MAP where user_id = :userId and usr_grp_id = :groupId",nativeQuery = true)
void deleteUserGroupMappingByUserAndGroup(#Param("userId") Long userId, #Param("groupId") Long groupId);
To this:
#Modifying
#Query(value = "delete from USR_USRGRP_MAP where user_id = :userId and usr_grp_id = :groupId",nativeQuery = true)
void deleteUserGroupMappingByUserAndGroup(#Param("userId") Long userId, #Param("groupId") Long groupId);
Cheers
~Emil

Resources