Have a strange problem and can't figure out how to deal with it.
Have simple POJO:
#Entity
#Table(name = "persons")
public class Person {
#Id
#GeneratedValue
private Long id;
#Column(name = "first_name")
private String firstName;
#Column(name = "middle_name")
private String middleName;
#Column(name = "last_name")
private String lastName;
#Column(name = "comment")
private String comment;
#Column(name = "created")
private Date created;
#Column(name = "updated")
private Date updated;
#PrePersist
protected void onCreate() {
created = new Date();
}
#PreUpdate
protected void onUpdate() {
updated = new Date();
}
#Valid
#OrderBy("id")
#OneToMany(mappedBy = "person", fetch = FetchType.EAGER, cascade = CascadeType.ALL, orphanRemoval = true)
private List<PhoneNumber> phoneNumbers = new ArrayList<>();
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getMiddleName() {
return middleName;
}
public void setMiddleName(String middleName) {
this.middleName = middleName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getComment() {
return comment;
}
public void setComment(String comment) {
this.comment = comment;
}
public Date getCreated() {
return created;
}
public Date getUpdated() {
return updated;
}
public List<PhoneNumber> getPhoneNumbers() {
return phoneNumbers;
}
public void addPhoneNumber(PhoneNumber number) {
number.setPerson(this);
phoneNumbers.add(number);
}
#Override
public String toString() {
return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE);
}
}
#Entity
#Table(name = "phone_numbers")
public class PhoneNumber {
public PhoneNumber() {}
public PhoneNumber(String phoneNumber) {
this.phoneNumber = phoneNumber;
}
#Id
#GeneratedValue
private Long id;
#Column(name = "phone_number")
private String phoneNumber;
#ManyToOne
#JoinColumn(name = "person_id")
private Person person;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getPhoneNumber() {
return phoneNumber;
}
public void setPhoneNumber(String phoneNumber) {
this.phoneNumber = phoneNumber;
}
public Person getPerson() {
return person;
}
public void setPerson(Person person) {
this.person = person;
}
#Override
public String toString() {
return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE);
}
}
and rest endpoint:
#ResponseBody
#RequestMapping(method = RequestMethod.GET)
public List<Person> listPersons() {
return personService.findAll();
}
In json response there are all fields except Id, which I need on front end side to edit/delete person. How can I configure spring boot to serialize Id as well?
That's how response looks like now:
[{
"firstName": "Just",
"middleName": "Test",
"lastName": "Name",
"comment": "Just a comment",
"created": 1405774380410,
"updated": null,
"phoneNumbers": [{
"phoneNumber": "74575754757"
}, {
"phoneNumber": "575757547"
}, {
"phoneNumber": "57547547547"
}]
}]
UPD Have bidirectional hibernate mapping, maybe it's somehow related to issue.
I recently had the same problem and it's because that's how spring-boot-starter-data-rest works by default. See my SO question -> While using Spring Data Rest after migrating an app to Spring Boot, I have observed that entity properties with #Id are no longer marshalled to JSON
To customize how it behaves, you can extend RepositoryRestConfigurerAdapter to expose IDs for specific classes.
import org.springframework.context.annotation.Configuration;
import org.springframework.data.rest.core.config.RepositoryRestConfiguration;
import org.springframework.data.rest.webmvc.config.RepositoryRestConfigurerAdapter;
#Configuration
public class RepositoryConfig extends RepositoryRestConfigurerAdapter {
#Override
public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {
config.exposeIdsFor(Person.class);
}
}
In case you need to expose the identifiers for all entities:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.rest.core.config.RepositoryRestConfiguration;
import org.springframework.data.rest.webmvc.config.RepositoryRestConfigurer;
import javax.persistence.EntityManager;
import javax.persistence.metamodel.Type;
#Configuration
public class RestConfiguration implements RepositoryRestConfigurer {
#Autowired
private EntityManager entityManager;
#Override
public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {
config.exposeIdsFor(
entityManager.getMetamodel().getEntities().stream()
.map(Type::getJavaType)
.toArray(Class[]::new));
}
}
Note that in versions of Spring Boot prior to 2.1.0.RELEASE you must extend the (now deprecated) org.springframework.data.rest.webmvc.config.RepositoryRestConfigurerAdapter instead of implement RepositoryRestConfigurer directly.
If you only want to expose the identifiers of entities that extends or
implements specific super class or interface:
...
#Override
public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {
config.exposeIdsFor(
entityManager.getMetamodel().getEntities().stream()
.map(Type::getJavaType)
.filter(Identifiable.class::isAssignableFrom)
.toArray(Class[]::new));
}
If you only want to expose the identifiers of entities with a specific annotation:
...
#Override
public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {
config.exposeIdsFor(
entityManager.getMetamodel().getEntities().stream()
.map(Type::getJavaType)
.filter(c -> c.isAnnotationPresent(ExposeId.class))
.toArray(Class[]::new));
}
Sample annotation:
import java.lang.annotation.*;
#Target(ElementType.TYPE)
#Retention(RetentionPolicy.RUNTIME)
public #interface ExposeId {}
Answer from #eric-peladan didn't work out of the box, but was pretty close, maybe that worked for previous versions of Spring Boot. Now this is how it is supposed to be configured instead, correct me if I'm wrong:
import org.springframework.context.annotation.Configuration;
import org.springframework.data.rest.core.config.RepositoryRestConfiguration;
import org.springframework.data.rest.webmvc.config.RepositoryRestConfigurerAdapter;
#Configuration
public class RepositoryConfiguration extends RepositoryRestConfigurerAdapter {
#Override
public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {
config.exposeIdsFor(User.class);
config.exposeIdsFor(Comment.class);
}
}
The class RepositoryRestConfigurerAdapter has been deprecated since 3.1, implement RepositoryRestConfigurer directly.
#Configuration
public class RepositoryConfiguration implements RepositoryRestConfigurer {
#Override
public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {
config.exposeIdsFor(YouClass.class);
RepositoryRestConfigurer.super.configureRepositoryRestConfiguration(config);
}
}
Font: https://docs.spring.io/spring-data/rest/docs/current-SNAPSHOT/api/org/springframework/data/rest/webmvc/config/RepositoryRestConfigurer.html
With Spring Boot you have to extends SpringBootRepositoryRestMvcConfiguration
if you use RepositoryRestMvcConfiguration the configuration define in application.properties may not worked
#Configuration
public class MyConfiguration extends SpringBootRepositoryRestMvcConfiguration {
#Override
protected void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {
config.exposeIdsFor(Project.class);
}
}
But for a temporary need
You can use projection to include id in the serialization like :
#Projection(name = "allparam", types = { Person.class })
public interface ProjectionPerson {
Integer getIdPerson();
String getFirstName();
String getLastName();
}
Just add #JsonProperty annotation to the Id and it works.
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#JsonProperty
private long id;
another approach is to implement RepositoryRestConfigurerAdapter in configuration. (This approach will be usefull when you have to do marshalling in many places)
#Component
public class EntityExposingIdConfiguration extends RepositoryRestConfigurerAdapter {
#Override
public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {
try {
Field exposeIdsFor = RepositoryRestConfiguration.class.getDeclaredField("exposeIdsFor");
exposeIdsFor.setAccessible(true);
ReflectionUtils.setField(exposeIdsFor, config, new ListAlwaysContains());
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
}
class ListAlwaysContains extends ArrayList {
#Override
public boolean contains(Object o) {
return true;
}
}
}
Hm, ok seems like I found the solution. Removing spring-boot-starter-data-rest from pom file and adding #JsonManagedReference to phoneNumbers and #JsonBackReference to person gives desired output. Json in response isn't pretty printed any more but now it has Id. Don't know what magic spring boot performs under hood with this dependency but I don't like it :)
Easy way: rename your variable private Long id; to private Long Id;
Works for me. You can read more about it here
Implement the RepositoryRestConfigurer and use #Configuration annotation on the class.
Here's the snippet
#Configuration
public class BasicConfig implements RepositoryRestConfigurer{
#Override
public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config, CorsRegistry cors) {
// TODO Auto-generated method stub
config.exposeIdsFor(Person.class);
}
}
You can also use the static configuration method to easily enable exposing ids in a few lines.
From the Spring Data Rest RepsositoryRestConfigurer docs:
static RepositoryRestConfigurer withConfig(Consumer<RepositoryRestConfiguration> consumer)
Convenience method to easily create simple RepositoryRestConfigurer instances that solely want to tweak the RepositoryRestConfiguration.
Parameters:
consumer - must not be null.
Since:
3.1
So this works for me in an existing #Configuration-annotated class:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.rest.webmvc.config.RepositoryRestConfigurer;
#Configuration
public class ApplicationConfiguration {
#Bean
public RepositoryRestConfigurer repositoryRestConfigurer() {
return RepositoryRestConfigurer.withConfig(repositoryRestConfiguration ->
repositoryRestConfiguration.exposeIdsFor(Person.class)
);
}
}
Related
I'm a bit new to Spring Boot and I'm trying to create model/repo/service/serviceImp/controller type of architecture.
After I try to make a this get request:
http://localhost:8080/api/v1/people/name?name=steve
and I get this error (I created a couple of people in DB):
"java.lang.NullPointerException: Cannot invoke \"com.project.Springbootbackend.service.PeopleService.findAllByName(String)\" because \"this.peopleService\" is null\r\n\tat com.project.Springbootbackend.controller.PeopleController.findAllByName(PeopleController.java:24)
This is my code:
People(entity)
#Entity
public class People {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#Column(name = "name")
private String name;
#Column(name = "surname")
private String surname;
#Column(name = "email")
private String email;
...
//constructor + get/set
PeopleController
#RestController
#RequestMapping("/api/v1/people")
#RequiredArgsConstructor
public class PeopleController {
private PeopleService peopleService;
#GetMapping("/name")
public ResponseEntity<List<People>> findAllByName(#RequestParam String name) {
return ResponseEntity.ok().body(peopleService.findAllByName(name));
}
}
PeopleRepo
public interface PeopleRepository extends JpaRepository<People, Integer> {
List<People> findAllByName(String name);
}
PeopleService
public interface PeopleService {
List<People> findAllByName(String name);
}
PeopleServiceImp
#RequiredArgsConstructor
#Service
public class PeopleServiceImp implements PeopleService {
PeopleRepository peopleRepository;
#Override
public List findAllByName(String name) {
return (List) ResponseEntity.ok(peopleRepository.findAllByName(name));
}
}
Thx guys in advance.
*SOLUTION:
Entity, service & repository is the same.
ServiceImp and controller changes are down belowe:
Controller:
#RestController
#RequestMapping("/api/v1/people")
public class PeopleController {
private PeopleService peopleService;
public PeopleController(PeopleService peopleService) {
this.peopleService = peopleService;
}
#GetMapping("/name")
public ResponseEntity<List<People>> findAllByName(#RequestParam String name) {
return ResponseEntity.ok().body(peopleService.findAllByName(name));
}
}
ServiceImp
#Service
public class PeopleServiceImp implements PeopleService {
private PeopleRepository peopleRepository;
public PeopleServiceImp(PeopleRepository peopleRepository) {
this.peopleRepository = peopleRepository;
}
#Override
public List<People> findAllByName(String name) {
List<People> people = peopleRepository.findAllByName(name);
return people;
}
}
Your constructor does not inject the service, because of the RequiredArgsConstructor (see Link) needs special treatment. Therefore, use final:
#RestController
#RequestMapping("/api/v1/people")
#RequiredArgsConstructor
public class PeopleController {
private final PeopleService peopleService;
#GetMapping("/name")
public ResponseEntity<List<People>> findAllByName(#RequestParam String name) {
return ResponseEntity.ok().body(peopleService.findAllByName(name));
}
}
Same here:
#RequiredArgsConstructor
#Service
public class PeopleServiceImp implements PeopleService {
private final PeopleRepository peopleRepository;
#Override
public List findAllByName(String name) {
return (List) ResponseEntity.ok(peopleRepository.findAllByName(name));
}
}
Additional hint, use a typed list:
#Override
public List<People> findAllByName(String name) {
return ResponseEntity.ok(peopleRepository.findAllByName(name));
}
Try like this:
#Autowired
private PeopleService peopleService;
#Autowired
private PeopleRepository peopleRepository;
You also need to add the #SpringBootApplication annotation in the main class of the application.
Something like that:
#SpringBootApplication
class PeopleApplication {
public static void main(String[] args) {
...
Take a look at this article about automatic dependency injection in Spring:
https://www.baeldung.com/spring-autowire
You missed the autowiring annotation in the controller to inject the service which may make this.peopleService to be null.
#Autowired
private PeopleService peopleService;
You also need to do autowire in your serviceimpl class
#Autowired
private PeopleRepository peopleRepository;
I wonder why I do not get this tutorial to work: https://www.baeldung.com/spring-boot-hibernate
I am using MySQL and the example is H2, but is that relevant?
I have the repository class like this:
import spring.boardgame.registerboardgame.model.gameList;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
#Repository
public interface FrontGameListRepository extends JpaRepository<gameList, Long> {
}
I have the service class like this:
#Service
public class DataFetcher {
#Autowired
private FrontGameListRepository frontgamelist;
public Iterable<gameList> fetchGameList(){
return this.frontgamelist.findAll();
}
}
And the entity class like this:
#Immutable
#Table(name = "gamelist")
public class gameList {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id", updatable = false, nullable = false)
private int id;
#Column
private String navn;
#Column
private int spillinger;
public gameList() {
}
public gameList(int nyid, String navne, int spillings) {
this.navn = navne;
this.id = nyid;
this.spillinger = spillings;
}
public int getId() {
return this.id;
}
public void setId(int id) {
this.id = id;
}
public float getSpillinger() {
return this.spillinger;
}
public void setSpillinger(int nyf) {
this.spillinger = nyf;
}
public void setNavn(String nynavn) {
this.navn = nynavn;
}
public String getNavn() {
return this.navn;
}
}
Anyone have any idea what I am doing wrong? I get a crash error that the frontgamelist object in the service class is null.
A new morning, new possibilities and I found the issue with fresh eyes. The issue was the first one pointed out in this post: https://www.moreofless.co.uk/spring-mvc-java-autowired-component-null-repository-service/
Even if this code was correct, I initiated the DataFetcher class manually in the controller, causing a null pointer exception in the next call.
import io.micronaut.core.annotation.NonNull;
import io.micronaut.data.annotation.GeneratedValue;
import io.micronaut.data.annotation.Id;
import io.micronaut.data.annotation.MappedEntity;
import io.micronaut.data.model.query.builder.sql.Dialect;
import io.micronaut.data.r2dbc.annotation.R2dbcRepository;
import io.micronaut.data.repository.reactive.ReactiveStreamsCrudRepository;
import io.micronaut.http.annotation.Body;
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;
import io.micronaut.http.annotation.Post;
import io.micronaut.runtime.Micronaut;
import lombok.RequiredArgsConstructor;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
public class Application {
public static void main(String[] args) {
Micronaut.run(Application.class, args);
}
}
#Controller
#RequiredArgsConstructor
class CustomerController {
private final CustomerRepository customerRepository;
#Get
public Flux<Customer> getAll() {
return customerRepository.findAll();
}
#Get("/test")
public Mono<Customer> test() {
return Mono.from(customerRepository.findById(1L)).map(e -> {
System.out.println(e);
return e;
});
// System.out.println(customerRepository);
// return Mono.just("TEST");
}
#Post
public Mono<Customer> create(#Body Customer customer) {
Customer saveCustomer = new Customer(customer.getName(), customer.getSurname());
return Mono.from(customerRepository.save(saveCustomer));
}
}
//#R2dbcRepository(dialect = Dialect.SQL_SERVER)
interface CustomerRepository extends ReactiveStreamsCrudRepository<Customer, Long> {
// #NonNull
// #Override
// Mono<Customer> findById(#NonNull #NotNull Long id);
//
// #NonNull
// #Override
// Mono<Customer> save(#NonNull #NotNull Customer customer);
//
#NonNull
#Override
Flux<Customer> findAll();
}
#MappedEntity("customer")
class Customer {
#Id
#GeneratedValue
private Long id;
private final String name;
private final String surname;
public Customer(String name, String surname) {
this.name = name;
this.surname = surname;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public String getSurname() {
return surname;
}
}
**
Trying to call a repositry through endpoint but keep getting error
**
Context does not contain key: io.micronaut.tx.STATUS
at reactor.util.context.Context1.get(Context1.java:67)
at io.micronaut.data.r2dbc.operations.DefaultR2dbcRepositoryOperations.lambda$withTransaction$27(DefaultR2dbcRepositoryOperations.java:441)
at reactor.core.publisher.FluxDeferContextual.subscribe(FluxDeferContextual.java:49)
at reactor.core.publisher.InternalFluxOperator.subscribe(InternalFluxOperator.java:62)
at reactor.core.publisher.FluxDefer.subscribe(FluxDefer.java:54)
at reactor.core.publisher.InternalFluxOperator.subscribe(InternalFluxOperator.java:62)
I've faced the same issue.
Check this:
#Get
public Flowable<Customer> getAll() {
return Flowable.fromPublisher(customerRepository.findAll());
}
and where is Mono use Single.fromPublisher(yourMono)
I encountered the same issue with micronaut 3.
After some tests i found that, in my specific case, this issue was caused by using the following modules:
micronaut-security-jwt
micronaut-reactor
micronaut-jackson-xml
If you remove all of these dependencies, the issue disappear.
I think that this problem is caused by the above micronaut modules, so i opened an issue on the micronaut-r2dbc github page with the hope they solve this issue ASAP.
EDIT:
they fixed this bug releasing the new micronaut version 3.0.1!!!
I have a basic spring boot data rest api with posts that can have many comments. My problem is that I cant seem to find a way to post my comment directly to the sub resource uri such as http://localhost:8090/posts/1/comments.
The only way I've been able to do it was to to create the comment resource first at http://localhost:8090/comments and then post the uri of comment into http://localhost:8090/posts/1/comments.
It seems like a really bad idea as comments should never be able to exist on their own and only ever linked to a post.
Does anybody know how I can do this as one action, otherwise I'll have to manually deal with potential orphaned comments where the comment gets posted but never gets posted into http://localhost:8090/posts/1/comments for whatever reason.
My code is below.
Any help would be massively appreciated.
#Entity
public class Comment extends ResourceSupport {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#JsonIgnore
private Long id;
private String comment;
#ManyToOne
private Post post;
#ManyToOne
private User sender;
protected Comment() {};
public void setId(Long id) {
this.id = id;
}
public String getComment() {
return comment;
}
public void setComment(String comment) {
this.comment = comment;
}
public User getSender() {
return sender;
}
public void setSender(User sender) {
this.sender = sender;
}
public Post getPost() {
return post;
}
public void setPost(Post post) {
this.post = post;
}
#Entity
public class Post extends ResourceSupport {
#Id
#GeneratedValue(strategy= GenerationType.AUTO)
private #JsonIgnore Long id;
private String text;
#OneToMany
private List<Comment> comments;
protected Post () {};
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
public List<Comment> getComments() {
return comments;
}
public void setComments(List<Comment> comments) {
this.comments = comments;
}
}
#RepositoryRestResource
public interface PostRepository extends PagingAndSortingRepository<Post, Long> {}
#RepositoryRestResource
public interface CommentRepository extends PagingAndSortingRepository<Comment, Long> {}
#SpringBootApplication
#EnableJpaRepositories("rest.api.repository")
#EnableWebMvc
#EnableTransactionManagement
#EnableAutoConfiguration
public class Application extends SpringBootServletInitializer{
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
the json im using to try to post the comment into the post is
{
"comment": "some text",
"sender": "http://localhost:8090/users/1"
}
It turned out that i needed to use mapped by i.e. #OneToMany(mappedBy = "post") on my comments list in my Post class.
Now i can post to http://localhost:8090/comments and then when i follow the http://localhost:8090/posts/1/comments link as before i now see the comments.
I wanted to enable some auditing features, such as #CreatedDate. I am not using Spring xml configuration file, so I cannot add mongo:auditing to Spring configuration. I was wondering if there was an alternative way of enable auditing. The following code is the model for user. But whenever I create a user, the date is not stored in the document, so the auditing it's not working. Could someone give me some help?
#Document(collection = "user")
public class User {
#Id
private String id;
#Indexed(unique = true)
private String email;
private String name;
#CreatedDate
private Date date;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getDate() {
return date;
}
public void setDate(Date date) {
this.date = date;
}
Because you are not using configuration via XML, I believe you are using annotations. You own a class like this:
public class MongoConfig extends AbstractMongoConfiguration {...}
Thus, in addition to the annotations you should already have, add: #EnableMongoAuditing
Your configuration class will look like this now:
#Configuration
#EnableMongoRepositories(basePackages="...")
#EnableMongoAuditing
public class MongoConfig extends AbstractMongoConfiguration {...}
I hope this helps!
That's all you need. No subclasses or other stuff.
#Configuration
#EnableMongoAuditing
public class AnyConfig {}
You should write a configuration class in which you can connect to MongoDB database using mongoClient by passing db url. and add the anootaion of #EnableMongoAuditing on top of that class.