I have a specific exercise to do within my university classes homework.
Given entity structure like this:
#Entity
#Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public abstract class Parent {
...some fields...
}
#Entity
public class ChildA extends Parent {
...
}
#Entity
public class ChildB extends Parent {
...
}
I have to implement JpaRepository method in ParentRepository or rather JPQL Query to perform update operation on table specified by Class<? extends Parent> parameter. Simply, method should update some field but only in a subclass given as a parameter.
The method structure should be:
public interface ParentRepository extends JpaRepository<Parent, Integer> {
void updateInBulk(Class<? extends Parent> clazz, int someParameter);
}
I came up with something like this, but it didn't work.
public interface ParentRepository extends JpaRepository<Parent, Integer> {
#Modifying
#Query("update :entity e set e.some_field = 'T' where e.other_field > :some_param")
void updateInBulk(#Param("entity") Class<? extends Parent> clazz, #Param("some_param") int someParam);
}
It was just my blind shot, but I could not find any materials which explain how to do stuff like this.
Does anybody have any suggestion how to solve this task?
You'll wouldn't do this with JPQL, but you'd rather want to write a Criteria query for that - which you can add to your JPARepository as well. Something like (you need to adjust this to your needs.)
public void updateInBulk(Class<? extends Parent> clazz, String someParameterName , int someValue) {
CriteriaBuilder cb = this.em.getCriteriaBuilder();
// create update
CriteriaUpdate<Order> update = cb.createCriteriaUpdate(clazz);
// set the root class
Root e = update.from(clazz);
// set update and where clause
update.set(someParameterName, someValue);
// update.where(cb.greaterThanOrEqualTo(e.get(someParameterName), oldValue));
// perform update
this.em.createQuery(update).executeUpdate();
}
Related
I have a repository
public interface PersonRepository extends JpaRepository<Person, Long> {}
and the Entity looks like this:
#Data
#Entity
#NoArgsConstructor
#AllArgsConstructor
public class Person {
#Id
private Long id;
#NotBlank
private String name;
}
I want to have a method which checks if all "persons" exist in database table by id, this what I have so far:
void checkIfAllPersonsExist(List<Long> personIds) {
var persons = personRepository.findAllById(personIds);
if (personIds.size() != persons.size()) {
personIds.removeAll(persons.stream().map(Persons::getId).collect(toList()));
throw new NotFoundException("Persons with id's [id:%s] does not exist", personIds);
}
}
I wonder if Spring JPA Repository can provide anything more elegant? Like specific named query which returns id's which does not exist?
If you want to just know that there are some ids that not exist you can count them
#Query("select COUNT(p.id) from Person p where p.id in :ids")
Long countIds(List<Long> ids);
Or equivalent based on
long countByIdIn(Collection<Long> ids);
Or return list of ids that exists
#Query("select p.id from Person p where p.id in :ids")
List<Long> getExistenIds(List<Long> ids);
And then filter out what you need.
personIds.removeAll(personRepository.getExistenIds(personIds));
if (!personIds.isEmpty()) {
throw new NotFoundException("Persons with id's [id:%s] does not exist", personIds);
}
First of all, your repository should extend JpaRepository<Person, Long> instead of JpaRepository<Person, String >, because your entity's id type is Long.
In and NotIn keywords can help you to achive your goal. Please check them out in this document: Query Creation - Spring Data JPA - Reference Documentation
I modified your code a little bit and it works for me.
Repository class:
public interface PersonRepository extends JpaRepository<Person, Long> {
List<Person> findByIdIn(Collection<Long> ids);
}
And sample snippet:
#Component
public class Bootstrap implements CommandLineRunner {
#Autowired
private PersonRepository repository;
#Override
public void run(String... args) throws Exception {
savePersons();
testFindMethod();
}
private void savePersons() {
Person person1 = Person.builder().id(1L).name("Name 1").build();
Person person2 = Person.builder().id(2L).name("Name 2").build();
Person person3 = Person.builder().id(3L).name("Name 3").build();
Person person4 = Person.builder().id(4L).name("Name 4").build();
repository.save(person1);
repository.save(person2);
repository.save(person3);
repository.save(person4);
}
private void testFindMethod() {
List<Long> toFind = new ArrayList<>();
toFind.add(1L);
toFind.add(2L);
toFind.add(3L);
checkIfAllPersonsExist(toFind);
toFind.add(7L);
checkIfAllPersonsExist(toFind);
}
void checkIfAllPersonsExist(List<Long> personIds) {
List<Person> persons = repository.findByIdIn(personIds);
if (personIds.size() != persons.size()) {
System.out.println("Sizes are different");
} else {
System.out.println("Sizes are same!");
}
}
}
And this is console output:
Sizes are same!
Sizes are different
I hope this will help you.
With this JPA repository method you can get the elements which ids doesn't exists:
List<Person> findByIdNotIn(List<Long> personIds);
If you want to remove them like in your example, you can use this one:
List<Person> deleteByIdNotIn(List<Long> personIds);
I hope it helps!
#Entity
public class A{
//some properties
}
#Entity
public class B{
//Some properties
}
I want to fetch selected columns from two tables using JPA, I know how to fetch single Entity table data through Repository and Controllers.
Repository:
public interface extends JPARepository<A, Long>{
List<A> findAll();}
Controller:
public class class_name{
#AutoWired
private JPARepository repo;
#RequestMapping("/data")
public List<A> getData(){
return repo.findAll();
}
}
Above code is to fetch single table data. Now, I want to fetch selected columns from both the tables.
Note: A, B Entities have mappings
What you can do is to use #Query annotation on one of your methods in the repository and performs something like this:
public Name {
String firstName;
String telephone;
public Name(String firstName,String telephon) {
//initialize fields
}
}
#Query(select new dummy.Name(u.name,c.telephone) from User u join fetch u.contact c where u.externalId= ?1 )
public Name getName(String externalId){}
You can return easily List instead of using constructor query , but i find it cleaner this way.
I'm running into something odd with inheritance and mongodbrepositories.
I have the following:
`
#Document
public class Base {
public String fieldA;
}
public class Derived extends Base {
public String fieldB;
}
public interface DerivedRepository extends MongoRepository<Base, String> {
List<Derived> findByFieldA(String fieldA);
}
`
When inserting i get
Inserting DBObject containing fields: [_class, _id, fieldA, fieldB ]
in collection: base
When i do findByFieldA('some value') on the repository i get the following:
find using query: { "fieldA" : "some value" } fields: null for class:
class Derived in collection: derived
Any idea what is going on here? And how can I fix this, either by saving it to the proper derived collection or by querying from the base collection.
Regards,
First, I would make Derived class as document since the parent is going to be shared among many implementations.
public class Base {
public String fieldA;
}
#Document
public class Derived extends Base {
public String fieldB;
#Override
public String toString() {
return "{fieldA: " + getFieldA() + ", fieldB: " + fieldB + "}";
}
}
Second, change the repository specification with the type of document (class marked as #Document) as:
public interface DerivedRepository extends MongoRepository<Derived, String> {
List<Derived> findByFieldA(String fieldA);
List<Derived> findByFieldB(String fieldB);
}
I added extra method findByFieldB(String fieldB) to explain more.
With these changes, you should be able to query either with fieldA or fieldB as below:
public class SpringBootMongoApplication {
#Autowired
private DerivedRepository derivedRepository;
public void testMethod() throws Exception {
Derived derived1 = new Derived();
derived1.setFieldB("fieldB1");
derived1.setFieldA("fieldA1");
Derived derived2 = new Derived();
derived2.setFieldB("fieldB2");
derived2.setFieldA("fieldA2");
this.derivedRepository.save(Arrays.asList(derived1, derived2));
List<Derived> deriveds = this.derivedRepository.findByFieldA("fieldA1");
System.out.println(deriveds);
List<Derived> deriveds1 = this.derivedRepository.findByFieldB("fieldB2");
System.out.println(deriveds1);
}
}
The output should be:
[{fieldA: fieldA1, fieldB: fieldB1}]
[{fieldA: fieldA2, fieldB: fieldB2}]
You can also verify the object persisted and their types with mongo query as below:
I have created an Spring Boot sample app which you can find in Github.
I can't get Spring Data Rest with class inheritance working.
I'd like to have a single JSON Endpoint which handles all my concrete classes.
Repo:
public interface AbstractFooRepo extends KeyValueRepository<AbstractFoo, String> {}
Abstract class:
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type")
#JsonSubTypes({
#JsonSubTypes.Type(value = MyFoo.class, name = "MY_FOO")
})
public abstract class AbstractFoo {
#Id public String id;
public String type;
}
Concrete class:
public class MyFoo extends AbstractFoo { }
Now when calling POST /abstractFoos with {"type":"MY_FOO"}, it tells me: java.lang.IllegalArgumentException: PersistentEntity must not be null!.
This seems to happen, because Spring doesn't know about MyFoo.
Is there some way to tell Spring Data REST about MyFoo without creating a Repository and a REST Endpoint for it?
(I'm using Spring Boot 1.5.1 and Spring Data REST 2.6.0)
EDIT:
Application.java:
#SpringBootApplication
#EnableMapRepositories
public class Application {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
I'm using Spring Boot 1.5.1 and Spring Data Release Ingalls.
KeyValueRepository doesn't work with inheritance. It uses the class name of every saved object to find the corresponding key-value-store. E.g. save(new Foo()) will place the saved object within the Foo collection. And abstractFoosRepo.findAll() will look within the AbstractFoo collection and won't find any Foo object.
Here's the working code using MongoRepository:
Application.java
Default Spring Boot Application Starter.
#SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
AbstractFoo.java
I've tested include = JsonTypeInfo.As.EXISTING_PROPERTY and include = JsonTypeInfo.As.PROPERTY. Both seem to work fine!
It's even possible to register the Jackson SubTypes with a custom JacksonModule.
IMPORTANT: #RestResource(path="abstractFoos") is highly recommended. Else the _links.self links will point to /foos and /bars instead of /abstractFoos.
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.EXISTING_PROPERTY, property = "type")
#JsonSubTypes({
#JsonSubTypes.Type(value = Foo.class, name = "MY_FOO"),
#JsonSubTypes.Type(value = Bar.class, name = "MY_Bar")
})
#Document(collection="foo_collection")
#RestResource(path="abstractFoos")
public abstract class AbstractFoo {
#Id public String id;
public abstract String getType();
}
AbstractFooRepo.java
Nothing special here
public interface AbstractFooRepo extends MongoRepository<AbstractFoo, String> { }
Foo.java & Bar.java
#Persistent
public class Foo extends AbstractFoo {
#Override
public String getType() {
return "MY_FOO";
}
}
#Persistent
public class Bar extends AbstractFoo {
#Override
public String getType() {
return "MY_BAR";
}
}
FooRelProvider.java
Without this part, the output of the objects would be separated in two arrays under _embedded.foos and _embedded.bars.
The supports method ensures that for all classes which extend AbstractFoo, the objects will be placed within _embedded.abstractFoos.
#Component
#Order(Ordered.HIGHEST_PRECEDENCE)
public class FooRelProvider extends EvoInflectorRelProvider {
#Override
public String getCollectionResourceRelFor(final Class<?> type) {
return super.getCollectionResourceRelFor(AbstractFoo.class);
}
#Override
public String getItemResourceRelFor(final Class<?> type) {
return super.getItemResourceRelFor(AbstractFoo.class);
}
#Override
public boolean supports(final Class<?> delimiter) {
return AbstractFoo.class.isAssignableFrom(delimiter);
}
}
EDIT
Added #Persistent to Foo.java and Bar.java. (Adding it to AbstractFoo.java doesn't work). Without this annotation I got NullPointerExceptions when trying to use JSR 303 Validation Annotations within inherited classes.
Example code to reproduce the error:
public class A {
#Id public String id;
#Valid public B b;
// #JsonTypeInfo + #JsonSubTypes
public static abstract class B {
#NotNull public String s;
}
// #Persistent <- Needed!
public static class B1 extends B { }
}
Please see the discussion in this resolved jira task for details of what is currently supported in spring-data-rest regarding JsonTypeInfo. And this jira task on what is still missing.
To summarize - only #JsonTypeInfo with include=JsonTypeInfo.As.EXISTING_PROPERTY is working for serialization and deserialization currently.
Also, you need spring-data-rest 2.5.3 (Hopper SR3) or later to get this limited support.
Please see my sample application - https://github.com/mduesterhoeft/spring-data-rest-entity-inheritance/tree/fixed-hopper-sr3-snapshot
With include=JsonTypeInfo.As.EXISTING_PROPERTY the type information is extracted from a regular property. An example helps getting the point of this way of adding type information:
The abstract class:
#Entity #Inheritance(strategy= SINGLE_TABLE)
#JsonTypeInfo(use=JsonTypeInfo.Id.NAME,
include=JsonTypeInfo.As.EXISTING_PROPERTY,
property="type")
#JsonSubTypes({
#Type(name="DECIMAL", value=DecimalValue.class),
#Type(name="STRING", value=StringValue.class)})
public abstract class Value {
#Id #GeneratedValue(strategy = IDENTITY)
#Getter
private Long id;
public abstract String getType();
}
And the subclass:
#Entity #DiscriminatorValue("D")
#Getter #Setter
public class DecimalValue extends Value {
#Column(name = "DECIMAL_VALUE")
private BigDecimal value;
public String getType() {
return "DECIMAL";
}
}
I have an abstract domain class containing a uid field, looking as below:
public abstract class GraphEntityWithUid extends GraphEntity {
private String uid = CommonUtils.newUid();
public String getUid() {
return uid;
}
public void setUid(String uid) {
this.uid = uid;
}
}
And, an abstract repository for it:
public abstract interface GraphEntityWithUidRepository<T extends GraphEntityWithUid> extends GraphRepository<T> {
public T findByUid(String uid);
}
I have a concrete domain class that inherits the uid, looking as below:
#NodeEntity
public class Attachment extends GraphEntityWithUid {
...
}
And, its repository looks as below:
public interface AttachmentRepository extends GraphEntityWithUidRepository<Attachment> {
}
Now, when I use the findByUid method as below:
// returns null
attachmentRepository.findByUid(uid);
it always returns null. However, if I re-declare the method in the AttachmentRepository as below, it works properly:
public interface AttachmentRepository extends GraphEntityWithUidRepository<Attachment> {
// Shouldn't this be automatically inherited??
public Attachment findByUid(String uid);
}
Why should I need to re-declare findByUid method in AttachmentRepository? Shouldn't it be automatically inherited from GraphEntityWithUidRepository?