Spring JPARepository Update a field - spring

I have a simple Model in Java called Member with fields - ID (Primary Key), Name (String), Position (String)
I want to expose an POST endpoint to update fields of a member. This method can accept payload like this
{ "id":1,"name":"Prateek"}
or
{ "id":1,"position":"Head of HR"}
and based on the payload received, I update only that particular field. How can I achieve that with JPARepository?
My repository interface is basic -
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
#Repository("memberRepository")
public interface MemberRepository extends JpaRepository<Member, Integer>{
}
My Member model -
#Entity
#Table(name="members")
public class Member {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name="member_id")
private Integer id;
#Column(name="member_name")
#NotNull
private String name;
#Column(name="member_joining_date")
#NotNull
private Date joiningDate = new Date();
#Enumerated(EnumType.STRING)
#Column(name="member_type",columnDefinition="varchar(255) default 'ORDINARY_MEMBER'")
private MemberType memberType = MemberType.ORDINARY_MEMBER;
public Member(Integer id, String name, Date joiningDate) {
super();
this.id = id;
this.name = name;
this.joiningDate = joiningDate;
this.memberType = MemberType.ORDINARY_MEMBER;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Date getJoiningDate() {
return joiningDate;
}
public void setJoiningDate(Date joiningDate) {
this.joiningDate = joiningDate;
}
public MemberType getMemberType() {
return memberType;
}
public void setMemberType(MemberType memberType) {
this.memberType = memberType;
}
public Member(String name) {
this.memberType = MemberType.ORDINARY_MEMBER;
this.joiningDate = new Date();
this.name = name;
}
public Member() {
}
}

Something like this should do the trick
public class MemberService {
#Autowired
MemberRepository memberRepository;
public Member updateMember(Member memberFromRest) {
Member memberFromDb = memberRepository.findById(memberFromRest.getid());
//check if memberFromRest has name or position and update that to memberFromDb
memberRepository.save(memberFromDb);
}
}

Related

Null value in primary key of hibernate entity

I faced with problem of null value in PK.
Here's an entity:
#Entity
#Table(name="space")
public class Space implements Serializable {
#Id
#GeneratedValue
#Column(nullable = false, unique = true)
private Long id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name="user_id")
private UserAccount user;
private String name;
private String description;
private Date createdTime;
private Date modifiedTime;
#OneToMany(mappedBy="space")
private Set<SpaceAccess> spaceAccesses = new HashSet<>();
public Set<SpaceAccess> getSpaceAccesses() {
return spaceAccesses;
}
public void setSpaceAccesses(Set<SpaceAccess> spaceAccesses) {
this.spaceAccesses = spaceAccesses;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Space() {}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public UserAccount getUser() {
return user;
}
public void setUser(UserAccount user) {
this.user = user;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public Date getCreatedTime() {
return createdTime;
}
public void setCreatedTime(Date createdTime) {
this.createdTime = createdTime;
}
public Date getModifiedTime() {
return modifiedTime;
}
public void setModifiedTime(Date modifiedTime) {
this.modifiedTime = modifiedTime;
}
}
I wrote strategy to generate PK properly but I always get Null in id field when I create new instance of the Space:
Space space = new Space();
Here's content of the object:
What i should do to generate id of instance properly using hibernate/spring mechanisms?
application.properties:
spring.datasource.url="some_url"
spring.datasource.username=name
spring.datasource.password=password
spring.jpa.generate-ddl=true
P.S. I use spring-boot-starter-data-jpa with version: 2.3.4.RELEASE.
Use:
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}

Parameter value [multiVLANSupport] did not match expected type [java.util.List (n/a)]

I have created an entity class that has a column which uses Attribute Converter of JPA:
#Convert(converter = StringListConverter.class)
private List<String> functionSpecificationLabel;
The converter class is :
#Converter
public class StringListConverter implements AttributeConverter<List<String>, String> {
#Override
public String convertToDatabaseColumn(List<String> list) {
return String.join(",", list);
}
#Override
public List<String> convertToEntityAttribute(String joined) {
return new ArrayList<>(Arrays.asList(joined.split(",")));
}
}
The expected values of the column in the Tables are like
functionSpecificationLabel
multiVLANSupport,telepresence,csaid
Now I need to return the rows that have multiVLANSupport,telepresence,csaid as value in functionSpecificationLabel column.
My Query in the repository for the same is :
#Query("Select pd from ProductDetailsEntity pd where pd.functionSpecificationLabel in (:labels)")
Optional<ProductDetailsEntity> findByFunctionSpecificationLabel(#Param("labels") final List<String> labels);
Now I face the issue as :
Parameter value [multiVLANSupport] did not match expected type [java.util.List (n/a)]
I am not exactly sure if this is even possible, here is how i have implemented to store list of values in an entity class using #ElementCollection You can read more about it here https://thorben-janssen.com/hibernate-tips-query-elementcollection/
A good discussion can be found here How to persist a property of type List<String> in JPA?. My suggestion is to avoid storing any values in db based on a delimiter.
Ideally, when storing such labels it is better to map them using OneToMany relationship. Also note that this will create an additional table in this case animal_labels.
Answer 1
Repository
#Repository
public interface AnimalRepository extends JpaRepository<Animal, UUID> {
List<Animal> findDistinctAnimalsByLabelsIsIn(List<String> cute);
}
Entity class
#Entity
#Table(name = "animal")
public class Animal {
#Id
#GeneratedValue
#Type(type = "uuid-char")
private UUID id;
private String name;
#ElementCollection(targetClass = String.class)
private List<String> labels;
public Animal() {
}
public Animal(String name, List<String> labels) {
this.name = name;
this.labels = labels;
}
public UUID getId() {
return id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<String> getLabels() {
return labels;
}
public void setLabels(List<String> labels) {
this.labels = labels;
}
}
Test:
#ExtendWith(SpringExtension.class)
#Transactional
#SpringBootTest(classes = TestApplication.class)
class CustomConverterTest {
#Autowired
private EntityManager entityManager;
#Autowired
private AnimalRepository animalRepository;
#Test
void customLabelConverter() {
Animal puppy = new Animal("Puppy", Arrays.asList("cute", "intelligent", "spy"));
Animal meow = new Animal("Cat", Arrays.asList("cute", "intelligent"));
entityManager.persist(puppy);
entityManager.persist(meow);
List<Animal> animalWithCutelabels = animalRepository.findDistinctAnimalsByLabelsIsIn(Arrays.asList("cute"));
List<Animal> animalWithSpylabels = animalRepository.findDistinctAnimalsByLabelsIsIn(Arrays.asList("spy"));
List<Animal> animalWithCuteAndSpylabels = animalRepository.findDistinctAnimalsByLabelsIsIn(Arrays.asList("cute", "spy"));
Assertions.assertEquals(2, animalWithCutelabels.size());
Assertions.assertEquals(1, animalWithSpylabels.size());
Assertions.assertEquals(2, animalWithCuteAndSpylabels.size());
}
}
Answer 2
If you do have any choice but to only go with the comma separated values then please find answer below for this approach:
Repository(since this is a string we cannot use list like in)
#Repository
public interface AnimalRepository extends JpaRepository<Animal, UUID> {
// Also note that the query goes as string and not list
List<Animal> findAllByLabelsContaining(String labels);
}
Test:
#Test
void customLabelConverter() {
Animal puppy = new Animal("Puppy", String.join(",", Arrays.asList("cute", "intelligent", "spy")));
Animal meow = new Animal("Cat", String.join(",", Arrays.asList("cute", "intelligent")));
entityManager.persist(puppy);
entityManager.persist(meow);
List<Animal> animalWithCutelabels = animalRepository.findAllByLabelsContaining(String.join(",", Arrays.asList("cute")));
List<Animal> animalWithSpylabels = animalRepository.findAllByLabelsContaining(String.join(",", Arrays.asList("spy")));
Assertions.assertEquals(2, animalWithCutelabels.size());
Assertions.assertEquals(1, animalWithSpylabels.size());
}
Entity:
#Entity
#Table(name = "animal")
public class Animal {
#Id
#GeneratedValue
#Type(type = "uuid-char")
private UUID id;
#Column
private String name;
#Column
private String labels;
public Animal() {
}
public Animal(String name, String labels) {
this.name = name;
this.labels = labels;
}
public UUID getId() {
return id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<String> getLabels() {
if (StringUtils.isEmpty(labels)) return Collections.emptyList();
return new ArrayList<>(Arrays.asList(labels.split(AnimalLabelsConverter.DELIMITER_COMMA)));
}
public void setLabels(List<String> labels) {
if (CollectionUtils.isEmpty(labels)) {
this.labels = "";
} else {
this.labels = String.join(AnimalLabelsConverter.DELIMITER_COMMA, labels);
}
}
#Converter
public static class AnimalLabelsConverter implements AttributeConverter<List<String>, String> {
private static final String DELIMITER_COMMA = ",";
#Override
public String convertToDatabaseColumn(List<String> labels) {
if (CollectionUtils.isEmpty(labels)) return "";
return String.join(DELIMITER_COMMA, labels);
}
#Override
public List<String> convertToEntityAttribute(String dbData) {
if (StringUtils.isEmpty(dbData)) return Collections.emptyList();
return new ArrayList<>(Arrays.asList(dbData.split(DELIMITER_COMMA)));
}
}
}

Why is my json returned from the controller with empty fields?

I am using the debugger in IntelliJ and right before the point of returning the result, the array is perfectly fine, as you can see here
But for some reason, the response in the browser looks like this
I don't understand why the fields are invisible.
This is what my 2 models look like:
Municipality:
#Entity
public class Municipality {
#Id
#JsonIgnore
#GeneratedValue(strategy = GenerationType.IDENTITY)
Long id;
String name;
}
Prediction
#Entity
public class Prediction {
#Id
#JsonIgnore
#GeneratedValue(strategy = GenerationType.IDENTITY)
Long id;
LocalDateTime tsPredictionMade;
LocalDateTime tsPredictionFor;
float pm10;
float pm25;
#ManyToOne
#OnDelete(action = OnDeleteAction.CASCADE)
Municipality municipality;
}
And this is my controller:
#RestController
#RequestMapping("/predict")
public class PredictionController {
private MunicipalityService municipalityService;
private PredictionService predictionService;
#Autowired
public PredictionController(MunicipalityService municipalityService, PredictionService predictionService) {
this.municipalityService = municipalityService;
this.predictionService = predictionService;
}
#GetMapping
public List<Municipality> getPredictions(){
List<Municipality> result = municipalityService.getPredictions();
return result;
}
#GetMapping("/{municipality}")
public List<Prediction> getPredictionsForMunicipality(#PathVariable("municipality") String name){
List<Prediction> result = predictionService.getPredictions(name);
return result;
}
}
The rest of the app (service and persistence layer) is pretty standard.
What is the reason for this?
You will need the getters and setters for your models. The Jackson library needs it for accessing its fields when converting the models into JSON, differently from JPA when converting the resultSet into models. Here is the code:
Prediction
#Entity
public class Municipality {
#Id
#JsonIgnore
#GeneratedValue(strategy = GenerationType.IDENTITY)
Long id;
String name;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public LocalDateTime getTsPredictionMade() {
return tsPredictionMade;
}
public void setTsPredictionMade(LocalDateTime tsPredictionMade) {
this.tsPredictionMade = tsPredictionMade;
}
public LocalDateTime getTsPredictionFor() {
return tsPredictionFor;
}
public void setTsPredictionFor(LocalDateTime tsPredictionFor) {
this.tsPredictionFor = tsPredictionFor;
}
public float getPm10() {
return pm10;
}
public void setPm10(float pm10) {
this.pm10 = pm10;
}
public float getPm25() {
return pm25;
}
public void setPm25(float pm25) {
this.pm25 = pm25;
}
public Municipality getMunicipality() {
return municipality;
}
public void setMunicipality(Municipality municipality) {
this.municipality = municipality;
}
}
Municipality
#Entity
public class Municipality {
#Id
#JsonIgnore
#GeneratedValue(strategy = GenerationType.IDENTITY)
Long id;
String name;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
You need getters and setter for each field that you want to expose.
You can use #Data from lombok project to avoid boilerplate code.
https://projectlombok.org/

how i can post json to springboot controller with #Embedded in #Embeddable class

the user entity
import javax.persistence.*;
#Entity
public class User {
#Id
#GeneratedValue
private Long id;
#Column(nullable = false)
private String name;
#Column(nullable = false)
private Integer age;
#Embedded
private Address address;
public User(){}
public User(String name, Integer age,Address address) {
this.name = name;
this.age = age;
this.address = address;
}
public User(String name, Integer age) {
this.name = name;
this.age = age;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
}
and the address entity
#JsonInclude(JsonInclude.Include.NON_NULL)
#Embeddable
public class Address {
private String city;
public Address() {
}
public Address( String city) {
this.city = city;
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
}
the controller code
#ResponseStatus(HttpStatus.CREATED)
#RequestMapping(value = "users", method = RequestMethod.POST)
public void users(#RequestBody List<User> users) {
this.userRepository.save(users);
}
when i post json data with psot man, the data is
[
{
"name":"yaohao",
"age":11,
"address":{
"city":"nantong"
}
},
{
"name":"yh",
"age":11,
"address":{
"city":"nantong"
}
}
]
the address always null
when the user entity has no #Embedded address entity,the code works fine,so how can i post json data to controller when i use #Embedded annotations
It has nothing to do with the use of #Embedded. When doing the marshaling Jackson uses Java Bean properties to set the values and as your User class is lacking a getAddress and setAddress Jackson simply ignores it because it doesn't exists.
To fix add the getter and setter for Address.
Or instead of using property access switch your mapper to use field access. See how to specify jackson to only use fields - preferably globally for more information on that.

Sprint Date Rest successful, but no data

Entity
#Data
#Accessors(chain = true, fluent = true)
#Entity
#Table(name = "T_NOTE")
#Access(AccessType.FIELD)
public class Note implements Serializable
{
#Id
#GeneratedValue
private Long id;
private Date date;
#Column(length = 2000)
private String content;
private String title;
private String weather;
}
Repository
#RepositoryRestResource(collectionResourceRel = "note", path = "note")
public interface NoteRepository extends AbstractRepository<Note, Long>
{
}
GET http://localhost:8080/note/2
{
"_links": {
"self": {
"href": "http://localhost:8080/note/2"
}
}
}
No entity field data, why?
EIDT
After I add standard setter/getter, everything is ok now.
public Long getId()
{
return id;
}
public void setId(Long id)
{
this.id = id;
}
public Date getDate()
{
return date;
}
public void setDate(Date date)
{
this.date = date;
}
public String getContent()
{
return content;
}
public void setContent(String content)
{
this.content = content;
}
public String getTitle()
{
return title;
}
public void setTitle(String title)
{
this.title = title;
}
public String getWeather()
{
return weather;
}
public void setWeather(String weather)
{
this.weather = weather;
}
Is this cause by jackson mapper ? How can I use fluent API with this ?Why not just use reflection to generate JSON ?
EDIT
What I need is this configuration
#Configuration
#Import(RepositoryRestMvcConfiguration.class)
public class ShoweaRestMvcConfiguration extends RepositoryRestMvcConfiguration
{
#Override
protected void configureJacksonObjectMapper(ObjectMapper mapper)
{
mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.NONE);
mapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY);
}
}
Caused by this
#Accessors is probably stepping over the #Data annotation, and with fluent = true it generates getters with the same name as the field, like id() and date() (#Accessor documentation). That's why Spring doesn't see any of the fields.
I think you can safely remove both #Accessors and #Access, since #Access's takes the default value from id (if you annotated the field, it will be FIELD, if you annotated the getter, it will be PROPERTY).

Resources