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.
Related
Well, I'm using Hibernate for the first time and, unexpectedly, it works. Except for one thing: an insert with a pk already inserted overwrite the record instaed of preventing it.
That's my simple code:
#Controller
public class SimpleController {
#Autowired
UserRepository userRepository;
#GetMapping("/mainPage")
public String viewMainPage(){
return "mainPage";
}
#GetMapping("/nuovo-utente")
public String viewInserisciUtente(Model model){
model.addAttribute("nuovoUtente", new Utente());
return "nuovo-utente";
}
#PostMapping("/nuovo-utente")
public String memorizzaUtente(#ModelAttribute Utente utente){
userRepository.save(utente);
return "output";
}
}
#Entity
public class Utente {
#Id
private int id;
private String citta=null;
private String genere=null;
private String data_nascita=null;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getCitta() {
return citta;
}
public void setCitta(String citta) {
this.citta = citta;
}
public String getGenere() {
return genere;
}
public void setGenere(String genere) {
this.genere = genere;
}
public String getData_nascita() {
return data_nascita;
}
public void setData_nascita(String data_nascita) {
this.data_nascita = data_nascita;
}
}
Any help will be appreciated.
EDIT: I've added the entity class to help you understanding my problem. Hoping that this will help.
Thanks you all
If you look at CrudRepository documentation, then we don't have update method, but we only have save method, which is used to add or update existing records.
In your case, you might have updated an entity (except its Id field) and tried saving the entity. So, CrudRepository will update the existing value for given Id, since it is already present.
Try adding ID generation strategy to id field.
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
I've been following a lot of tutorial on how to get a list of result by referencing a specific column in the table.
I have this table.
I want to get the list of result with a plan_code "TEST123"
This is my code:
PlanRepository.java
public interface PlanCoverageRepository extends CrudRepository<PlanCoverage, Long> {
List<PlanCoverage> findAllByPlan_code(String plan_code);
}
PlanCoverageService.java
public interface PlanCoverageService {
public List<PlanCoverage> getAllPlanCoverageByPlanCode(String plan_code);
}
PlanCoverageServiceImpl.java
#Service
#Transactional
public class PlanCoverageServiceImpl implements PlanCoverageService {
#Override
public List<PlanCoverage> getAllPlanCoverageByPlanCode(String plan_code) {
return (List<PlanCoverage>) planCoverageRepository.findAllByPlan_code(plan_code);
}
}
PlanCoverageController.java
#Controller
#RequestMapping(value="/admin")
public class PlanCoverageController {
#Autowired
PlanCoverageService planCoverageService;
#RequestMapping(value="/Test/{plan_code}", method=RequestMethod.GET)
public ModelAndView test(#PathVariable String plan_code) {
ModelAndView model = new ModelAndView();
PlanCoverage planCoverage = (PlanCoverage) planCoverageService.getAllPlanCoverageByPlanCode(plan_code);
model.addObject("planCoverageForm",planCoverage);
model.setViewName("plan_coverage_form");
return model;
}
}
PlanCoverage.java
#Entity
#Table(name="plan_coverage")
public class PlanCoverage {
#Id
#GeneratedValue(strategy= GenerationType.IDENTITY)
private long coverage_id;
#Column(name="plan_code")
private String plan_code;
#Column(name="coverage_description")
private String coverage_description;
/..getters and setters
#ManyToOne()
#JoinColumn(name="plan_code", referencedColumnName = "plan_code",insertable=false, updatable=false)
private Plan plan;
public Plan getPlan() {
return plan;
}
public void setPlan(Plan plan) {
this.plan = plan;
}
}
Please help me. I've been stuck with these for a few days and non of the tutorials seems to work on me. Thank you so much!!
You have messed up with the convention that spring boot is using to compose query methods. The case of the fields in the entity should follow the lower camel-case scheme, like so:
#Column(name="plan_code")
private String planCode;
and then the query method in PlanCoverageRepository should be:
List<PlanCoverage> findAllByPlanCode(String planCode);
I have some tables :
PROFIL : id_profil, ...
EXPERIENCE : id_experience, id_profil#, ...
COMPETENCE_LEVEL : id_competence_level, level, ...
One PROFIL can have lot of EXPERIENCE and lot of COMPETENCE_LEVEL.
One EXPERIENCE can have lot of COMPETENCE_LEVEL.
One COMPETENCE_LEVEL concerns lot of EXPERIENCE.
So, for me, between EXPERIENCE and COMPETENCE_LEVEL, this is a (n-p) ManyToMany relation.
I tried:
PROFIL.java:
#Entity
#Table(name="profil")
public class Profil {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
#Column(name="id_profil")
private Long idProfil;
public Profil() {
super();
}
public Long getIdProfil() {
return idProfil;
}
public void setIdProfil(Long idProfil) {
this.idProfil = idProfil;
}
#Override
public String toString() {
//[...]
}
}
EXPERIENCE.java:
#Entity
#Table(name="experience")
public class Experience {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
#Column(name="id_experience")
private Long idExperience;
#ManyToOne
#JoinColumn(name="id_profil")
private Profil idProfil;
private List<CompetenceLevel> competenceLevels;
public Experience() {
super();
idProfil = new Profil();
}
public Long getIdExperience() {
return idExperience;
}
public void setIdExperience(Long idExperience) {
this.idExperience = idExperience;
}
public Profil getIdProfil() {
return idProfil;
}
public void setIdProfil(Profil idProfil) {
this.idProfil = idProfil;
}
#ManyToMany(cascade = CascadeType.ALL)
#JoinTable(name = "experience_competence_level", joinColumns = #JoinColumn(name = "id_experience", referencedColumnName = "id_experience"), inverseJoinColumns = #JoinColumn(name = "id_competence_level", referencedColumnName = "id_competence_level"))
public List<CompetenceLevel> getCompetenceLevels() {
return competenceLevels;
}
public void setCompetenceLevels(List<CompetenceLevel> competenceLevels) {
this.competenceLevels = competenceLevels;
}
#Override
public String toString() {
// [...]
}
}
COMPETENCE_LEVEL.java:
#Entity
#Table(name="competence_level")
public class CompetenceLevel {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
#Column(name="id_competence_level")
private Long idCompetenceLevel;
#OneToOne
#JoinColumn(name="id_level")
private Level level;
#OneToOne
#JoinColumn(name="id_profil")
private Profil profil;
private List<Experience> experiences;
public CompetenceLevel() {
super();
}
public Long getIdCompetenceLevel() {
return idCompetenceLevel;
}
public void setIdCompetenceLevel(Long idCompetenceLevel) {
this.idCompetenceLevel = idCompetenceLevel;
}
public Level getLevel() {
return level;
}
public void setLevel(Level level) {
this.level = level;
}
public Profil getProfil() {
return profil;
}
public void setProfil(Profil profil) {
this.profil = profil;
}
#ManyToMany(mappedBy = "competenceLevels")
public List<Experience> getExperiences() {
return experiences;
}
public void setExperiences(List<Experience> experiences) {
this.experiences = experiences;
}
#Override
public String toString() {
// [...]
}
}
So, I have this error :
org.hibernate.MappingException: Could not determine type for: java.util.List, at table: competence_level, for columns: [org.hibernate.mapping.Column(experiences)]
I don't understand why. I follow this tuto : https://hellokoding.com/jpa-many-to-many-relationship-mapping-example-with-spring-boot-maven-and-mysql/
Do you have an idea ? Thanks.
The reason is simply: don't mix field and method annotations in the same persistent class.
Hibernate generates an unclear error here. It is very hard to figure out the reason of the error, if you don't face it before.
In your code, you are mixing field access and property access. See this answer.
I would prefer using only one of the possibilities. I use field annotations, like you did for idProfil.
In the book "Professional Java for Web Applications" by Nicholas S. Williams (very, very good) I found this:
You should never mix JPA property annotations and JPA field
annotations in the same entity. Doing so results in unspecified
behaviour and is very likely to cause errors.
And just for clearness, I wouldn't write this
#ManyToOne
#JoinColumn(name="id_profil")
private Profil idProfil;
// better:
// private Profil profil;
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)
);
}
}
I have application separated to frontend and backend modules which communicate through restfull webservice. Unfortunately, something goes wrong in this code and I get from Backend part:
java.lang.ClassCastException: com.rrd.ecomdd.data.SharedFile cannot be cast to javax.xml.bind.JAXBElement
Frontend snippet:
#Override
public void share(Set<SharedFile> fileSet) {
apiTarget.path(ApiConstant.FILESERVICE)
.path(ApiConstant.FILESERVICE_SHARE)
.request(MediaType.APPLICATION_JSON_TYPE.withCharset("UTF-8"))
.post(Entity.entity(fileSet.toArray(new SharedFile[0]), MediaType.APPLICATION_JSON_TYPE.withCharset("UTF-8")), new GenericType<Set<SharedFile>>() {
});
}
Backend snippet
#POST
#Path(ApiConstant.FILESERVICE_SHARE)
#Produces("application/json; charset=UTF-8")
#Consumes("application/json; charset=UTF-8")
public List<SharedFile> share(SharedFile[] sharedList) {
for (SharedFile s : sharedList) {
fileService.share(s);
}
return Arrays.asList(sharedList);
}
SharedFile class:
public class SharedFile {
private Long id;
private User user;
private ManagedFile file;
private UUID uuid = UUID.randomUUID();
public SharedFile(User user, ManagedFile file) {
this.user = user;
this.file = file;
}
public SharedFile() {
}
//getters, setters, equals and hashcode below
}
Any ideas how to fix this?
Try to annotate the class and its attributes as mentioned here:
#XmlRootElement
public class SharedFile {
#XmlElement
private Long id;
#XmlElement
private User user;
#XmlElement
private ManagedFile file;
Follow this for more: http://docs.oracle.com/javaee/6/tutorial/doc/gkknj.html