The domain model is
An industry has many companies
A company belongs to an industry
So I have my entity classes:
#Entity
public class Industry {
#Id #GeneratedValue
private Long id;
private String name;
#OneToMany(targetEntity = Company.class, fetch = FetchType.LAZY, mappedBy = "industry")
private Collection<Company> companies = new ArrayList<>(0);
// Getters and setters
}
and
#Entity
public class Company {
#Id #GeneratedValue
private Long id;
private String name;
#ManyToOne(cascade = CascadeType.DETACH, fetch = FetchType.EAGER, optional = false)
private Industry industry;
// Getters and setters
}
My controller:
#RestController
#RequestMapping("/companies")
public class CompaniesController extends ControllerBase {
#RequestMapping(method = RequestMethod.POST)
public Company create(#RequestBody Company company) {
company.getIndustry(); // returns null
// ...
}
}
When I send request POST /companies with request body
{
"name": "Walmart",
"industry": {
"id": 1
}
}
I found that company.getIndustry() always returns null. How can I make the controller accept nested entities?
Entities are session based. They usually work on basis of Lazy loading I.e only the first level is loaded and other attributes are loaded on Demand. You cannot pass it from one layer to other. (service to controller)
The correct way to do it. Have a Value object (a simple class) n the controller. Use it between front end and back end. Send the same value object to service. And use the entity only between Service and DAo layer
public class CompanyVO{
private Long id;
private String name;
private IndustryVO industryVO; // create similar class
// Getters and setters
}
#RestController
#RequestMapping("/companies")
public class CompaniesController extends ControllerBase {
#RequestMapping(method = RequestMethod.POST)
public Company create(#RequestBody CompanyVO companyVO) {
companyVO.getIndustry(); // returns null
// ...
}
}
This may be because you need another Spring message converter instead of the default one. Just add jackson to your pom.xml and Spring will use MappingJackson2HttpMessageConverter.
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.8.5</version>
</dependency>
Related
I am using Spring Boot 2. I want to add certain dynamic validations at server side which should be executed in POST REST call. Can these validations be added in form of annotations at parameter level of POST method call?
Below code will help you to achieve server side validation.
Pojo class :
public class Data {
#NotNull
private final String someStringValue;
#Min(1)
private final int someIntValue;
#JsonCreator
public Data(#JsonProperty("someStringValue") String someStringValue, #JsonProperty("someIntValue") int someIntValue) {
this.someStringValue = someStringValue;
this.someIntValue = someIntValue;
}
public String getSomeStringValue() {
return someStringValue;
}
public int getSomeIntValue() {
return someIntValue;
}
For the validation we need a custom class containing the logic:
#Component
public class StringValueValidator {
public void validate(String language, Data data, Errors errors) {
if (!"de-DE".equals(language)) {
if (data.getSomeStringValue().length() > 140) {
errors.reject("someStringValue");
}
}
}
}
controller method :
#RequestMapping(value = "/validation", method = RequestMethod.POST)
public ResponseEntity<?> acceptData(#Valid #RequestBody Data data, Errors errors,
#RequestHeader(HttpHeaders.ACCEPT_LANGUAGE) String language) {
stringValueValidator.validate(language, data, errors);
if (errors.hasErrors()) {
return new ResponseEntity<>(createErrorString(errors), HttpStatus.BAD_REQUEST);
}
return new ResponseEntity<>(HttpStatus.OK);
}
Refer this : https://blog.codecentric.de/en/2017/11/dynamic-validation-spring-boot-validation/
Here is how you can achieve to defining validations in RequestBody you are receiving from client side.
Define your POJO you will receive from client side.
public class Employee implements Serializable {
private Long id;
#NotNull(message = "Name should not be null")
#NotBlank(message = "Name should be not empty")
#Size(min = 1, max = 255, message = "Name allowed max 255 characters")
private String name;
Now in your controller use #Valid annotation for #RequestBody like below.
public ResponseEntity<?> createEmployee(#Valid #RequestBody Employee employee)
I am try to upaload a file with some other fields for model, but I can't binding file with those fields.
handling request.
#RequestMapping(value="/products", method=RequestMethod.POST)
public String saveProduct(#Valid #ModelAttribute("product") Product product, BindingResult result){
if (result.hasErrors()) {
return "manageProduct";
}
}
in model class with some other fields
#Entity
public class Product {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private String code;
#NotBlank(message = "Product name can't be blank")
private String name;
#NotBlank(message = "Brand name can't be blank")
private String brand;
#NotBlank(message = "Description can't be blank.")
#Size(min = 10, max = 500, message = "Description shoud be contain 10 and 500 characters")
#JsonIgnore
private String description;
#Min(value = 1)
#Column(name = "unit_price")
private float unitPrice;
private int quantity;
#JsonIgnore
#Column(name = "is_active")
#OrderBy("id ASC")
private boolean active;
#Column(name = "category_id")
#JsonIgnore
private int categoryId;
#Column(name = "supplier_id")
#JsonIgnore
private int supplierId;
private int purchases = 0;
private int views = 0;
#Transient
private MultipartFile file;
//getters and setters
}
form also enctype="multipart/form-data" and method is "post".
When try to upload file with others fields, then errors occurs.
And I am using spring boot.
Assuming you are using Spring Data and JPA then Spring Content provides a solution to uploading and associating content with your entity.
As you are using Spring Boot it is easy to add. Add the following dependencies:
pom.xml
<!-- Java API -->
<dependency>
<groupId>com.github.paulcwarren</groupId>
<artifactId>spring-content-jpa-boot-starter</artifactId>
<version>0.0.11</version> <!-- Or 0.1.0 for Spring Boot 2 -->
</dependency>
<!-- REST API -->
<dependency>
<groupId>com.github.paulcwarren</groupId>
<artifactId>spring-content-rest-boot-starter</artifactId>
<version>0.0.11</version> <!-- Or 0.1.0 for Spring Boot 2 -->
</dependency>
Add the following attributes to your Product entity so that content can be associated with it (this is in place of your Multipart file field):
Product.java
#Entity
public class Product {
...existing fields...
#ContentId
private String contentId;
#ContentLength
private long contentLength = 0L;
#MimeType
private String mimeType;
// no need for MultipartFile
Create a ContentStore (the equivalent of a JpaRepository for BLOBs):
ProductContentStore.java
#StoreRestResource(path="productsContent")
public interface ProductContentStore extends ContentStore<Product, String> {
}
When you run your application Spring Content will see the ProductContentStore interface and the spring-content-jpa dependency and inject an JPA implementation of this interface for you meaning that you don't have to write this yourself. It will also see the spring-content-rest dependency and add an #Controller implementation that forwards GET, PUT, POST and DELETE REST requests onto the ProductContentStore bean meaning that you don't have to write this controller either. REST endpoints will be available at /productsContent so...
after creating your product and getting an ID, you can then:
curl -X POST -F "image=#/some/local/path/product.jpg" /productsContent/{productId}
to upload product.jpg and associate it with your product entity. And:
curl /productsContent/{productId} will fetch it again.
Yes, this does imply that you need TWO requests to create a product and associate an image with it. If you have valid reasons that justify why this needs to be one request and not two then raise an issue against Spring Content github repo and we can look into adding support this for you.
HTH
This is a REST XML service. On Save, the foreign key value in child table is empty.
The #Id's are using sequence and it works fine. Im not added the sequence generator code here.
//Main Entity
------------
#Entity
#Table(name="REQUEST")
public class MsaDisabScreenRequest implements Serializable {
#Id
#Column(name="REQUEST_ID")
private long requestId; //sequence
#OneToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE }, fetch = FetchType.LAZY, mappedBy="msaDisabScreenRequest")
private Set<ReqDetail> disabilities;
}
Child Entity
#Entity
#Table(name="REQ_DETAILS")
public class ReqDetail implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#Column(name="MAP_ID")
private long mapId; //sequence
#Column(name="TYPE_ID")
private long disabilityTypeId;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name="REQUEST_ID")
privateRequest msaDisabScreenRequest;
}
This is the dto that I'm using to map.
//Main Dto
#XmlRootElement(name="DisabilityRequest")
#XmlAccessorType(XmlAccessType.FIELD)
public class MsaDisabScreenRequestDto implements Serializable {
private static final long serialVersionUID = 1L;
private long requestId;
#NotNull
private Set<DetailDto> disabilities;
}
//Child Dto
#XmlRootElement(name="disabilities")
#XmlAccessorType(XmlAccessType.FIELD)
public class MsaDisabScreenReqDetailDto implements Serializable {
private static final long serialVersionUID = 1L;
private long mapId;
private long disabilityTypeId;
#XmlTransient
private RequestDto msaDisabScreenRequest;
}
This is the controller
#RequestMapping(value = ApiPath.REQUEST, method = RequestMethod.POST, produces = { "application/xml"})
public #ResponseBody ResultDecorator saveScreeningRequest(#Valid #RequestBody RequestDto requestDto) throws Exception {
.
.
.
.
}
save code which is using jpa repository to persist.
Implementation code only added the code releavant to save
#Autowired
private OrikaBeanMapper mapper;
.
.
.
.
.
.
.
// mapping
Request request = mapper.map(requestDto,Request.class);
Request Res = msaRepository.save(request);
This is the request payload I'm sending
<?xml version="1.0" encoding="UTF-8" ?>
<DisabilityRequest>
<disabilities>
<disabilityTypeId>9</disabilityTypeId>
</disabilities>
</DisabilityRequest>
Here the requestId is added as empty REQ_DETAILS table. All other entries passed are persisted.
Let me know if you need any further details.
When fetch type is LAZY, the data set are empty. You can use Fetch type EAGER but its expensive
I have a 2 domain classes one with reference to another like this:
#Document
public class Dummy {
#Id
private UUID id;
private String name;
#Reference
private DummyAttribute dummyAttribute;
// getters and setters omitted.
}
#Document
public class DummyAttribute {
#Id
private UUID id;
private String name;
// getters and setters omitted.
}
I also have 2 repositories corresponding to Dummy and DummyAttribute.
public interface DummyRepository extends CrudRepository<Dummy, UUID> {
}
public interface DummyAttributeRepository extends
CrudRepository<DummyAttribute, UUID> {
}
I want to create a Dummy with a DummyAttribute. So, I create a dummyAttribute by posting to /dummyattributes. I get the response body with a self link to dummyAttribute back. This self link that I get back is used during the creation of Dummy. My JSON payload to the /dummies looks like :
{
"name" : "dummy",
"dummyAttribute" : <self link of dummyAttribute generated during POST>
}
When I do a GET on the association URL generated after POST, I correctly get
the dummyAttribute that was used. So far works well in Spring Data REST.
I want to do the same using my custom controllers. So, I created controllers
for both Dummy and DummyAttribute.
#RestController
public class DummyController {
#Autowired
private DummyRepository dummyRepository;
#Autowired
private DummyResourceProcessor processor;
#RequestMapping(value = "/dummies", method = RequestMethod.POST)
public HttpEntity<Resource<Dummy>> createTenant(#RequestBody Dummy dummy)
{
Dummy save = dummyRepository.save(dummy);
Resource<Dummy> dummyr = new Resource<Dummy>(save);
return new ResponseEntity<Resource<Dummy>>(processor.process(dummyr),
HttpStatus.CREATED);
}
#RequestMapping(value = "/dummies/{id}", method = RequestMethod.GET)
public HttpEntity<Resource<Dummy>> getDummy(#PathVariable("id") Dummy
dummy) {
Resource<Dummy> dummyr = new Resource<Dummy>(dummy);
return new ResponseEntity<Resource<Dummy>>(processor.process(dummyr),
HttpStatus.OK);
}
#RestController
public class DummyAttributeController {
#Autowired
private DummyRepository dummyRepository;
#Autowired
private DummyAttributeRepository dummyAttributeRepository;
#Autowired
private DummyAttributeResourceProcessor processor;
#RequestMapping(value = "/dummyAttributes", method =
RequestMethod.POST)
public HttpEntity<Resource<DummyAttribute>> createDummyAttribute(
#RequestBody DummyAttribute dummyAttribute) {
DummyAttribute save = dummyAttributeRepository.save(dummyAttribute);
Resource<DummyAttribute> dummyr = new Resource<DummyAttribute>(save);
return new ResponseEntity<Resource<DummyAttribute>>
(processor.process(dummyr),createHeaders(request,save.getId()),
HttpStatus.CREATED);
}
#RequestMapping(value = "/dummyAttributes/{id}", method =
RequestMethod.GET)
public HttpEntity<Resource<DummyAttribute>> getDummyAttribute(
#PathVariable("id") DummyAttribute dummyAttribute) {
Resource<DummyAttribute> dummyr = new Resource<DummyAttribute>
(dummyAttribute);
return new ResponseEntity<Resource<DummyAttribute>>
(processor.process(dummyr), HttpStatus.OK);
}
I followed the same sequence of step as above. I did a POST todummyAttribute.
Using this self link , I tried to create a dummy.
This time things are not so smooth. I get this exception back.
Can not instantiate value of type [simple type,
class com.sudo.DummyAttribute] from String value
('http://localhost:8080/dummyAttributes/3fa67f88-f3f9-4efa-a502-
bbeffd3f6025'); no single-String constructor/factory method at
[Source: java.io.PushbackInputStream#224c018a; line: 2, column: 19]
(through reference chain: com.sudo.Dummy["dummyAttribute"])
When I create a constructor inside DummyAttribute, and I parse the id from the url and assign it to the id.
public DummyAttribute(String url) {
String attrId = // parse the URL to get the id;
this.id = attrId;
}
Now things are work as expected.The dummyAttribute is assigned to dummy.
What I would like to know is why are things different when I write my custom-controller ? What am I missing ? How is it that when I use Spring Data REST, the reference URL to the dummyAttribute was automatically resolved to the corresponding dummyAttribute object and in the custom controller, I had to parse it manually and assign the id value explicitly to domainAttribute id?
Also, in the constructor I believe, the dummyAttribute is not resolved by finding it from repository by doing a findOne but a new dummyAttribute is being created. Is this correct?
How do I make my POSTs to my custom controller work exactly like how it works in Spring Data REST ? Do I need a custom serializer/deserializer for this ? Do I need to register some components manually and invoked it ?
I found that when I have customer controllers and #EnableWebMvc is used, the associated resource does not get resolved. That results in the error above. If no #EnableWebMvc is present, then the associated resource gets resolved properly. Not sure how #EnableWebMvc gets in between....
The spring versions that I use are : spring-boot-starter-1.2.5, spring-boot-starter-data-rest-1.2.5, spring-data-commons-1.9.3. spring-hateoas-0.16.0, spring-data-rest-core-2.2.3, spring-data-rest-webmvc-2.2.3.
In my Spring app i have Two entity:
#Entity
public class Category {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int id;
....
#OneToOne
#Cascade({CascadeType.ALL})
#JoinColumn(name="page_id")
private Page page;
#OneToMany(fetch=FetchType.LAZY, mappedBy="category")
private List<Shop> shop;
..getters and setters
}
And
#Entity
public class Shop {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int id;
....
#OneToOne(mappedBy="shop")
private Settings settings;
#ManyToOne
#Cascade({CascadeType.SAVE_UPDATE})
#JoinColumn(name = "category_id")
private Category category;
#OneToOne
#Cascade({CascadeType.ALL})
#JoinColumn(name = "page_id")
private Page page;
...getters and setters
}
And in my test jUnit i added some category and some shop with this category, when i want to access to list of shops current category i have a error
java.lang.NullPointerException ....
My test:
#ContextConfiguration(locations = {
"classpath:security-context.xml",
"classpath:dao-context.xml"
})
#RunWith(SpringJUnit4ClassRunner.class)
#WebAppConfiguration
#TransactionConfiguration(transactionManager="transactionManager", defaultRollback=false)
#Transactional
public class CategoryTest {
Shop shop1 = new Shop(....);
Shop shop1 = new Shop(....);
Category category1 = new Category(....);
Category category2 = new Category(....);
#Test
public void someTest(){
shop1.setCategory(category1);
shop2.setCategory(category2);
shopService.add(shop1);
shopService.add(shop2);
assertEquals(2, shopService.getAllShop().size());
assertEquals(2, categoryService.getAllShop().size());
//IN THIS LINE I HAVE A ERROR
assertEquals(1, categoryService.getCategory(category1.getId()).getShop().size());
}
}
For access to lazy atribute i added to my web.xml filtr: org.springframework.orm.hibernate3.support.OpenSessionInViewFilter This working properly in front app not in jUnit test.
Which point I make an error?
The lazy loading only takes place whist inside a transaction, when the session is open. So you get null in the test method unless you initialize the lazy loaded collection beforehand.
Another similar question