I'm trying to create CRUD endpoints for a model and i feel little confused about the type of request I should use.
Let's say that this is my Entity:
#Entity
#Data
#Builder
#NoArgsConstructor
#AllArgsConstructor
public class Language {
#Id
#JsonProperty("language_id")
private UUID languageId;
#JsonProperty("language_name")
private String languageName;
#JsonProperty("abbreviation")
private String abbreviation;
#JsonProperty("created_at")
private LocalDateTime createdAt;
#JsonProperty("modified_at")
private LocalDateTime modifiedAt;
}
and i want to develop an endpoint in the Controller to create new language. The user should provide only the language name and the language abbreviation.
I've tried with this one :
#Operation(
summary = "Create a new language",
description = "This endpoint is used to create a new language.\n" +
"\n" +
"| Parameter | Type | Description\n" +
"| - | - | -\n" +
"| language_name | String | The name of the language (arabic, french ...)\n" +
"| languageAbbrev | String | The abbreviation of the language (AR, FR...)\n",
responses = {
#ApiResponse(
description = "| Parameter | Type | Description\n" +
"| - | - | -\n" +
"|language_id| UUID | The unique identifier for the language\n" +
"| language_name | String | The name of the language (French, English ...)\n" +
"| languageAbbrev | String | The abbreviation of the language (FR, EN...)\n" +
"| created_at | LocalDateTime | The first time the language was created\n" +
"| modified_at | LocalDateTime | The Last time the language was modified. If it's never modified it will return the created_at value\n",
responseCode = "200",
content = { #Content(mediaType = "application/json",
schema = #Schema(implementation = CreatedSKillDTO.class)) }),
#ApiResponse(description = "language already exists", responseCode = "403", content = #Content),
#ApiResponse(description = "Internal server error", responseCode = "500", content = #Content)
}
)
#PostMapping("/")
public ResponseEntity<Language> addNewLanguage(#RequestParam("languageName") String languageName,
#RequestParam("languageAbbrev") String languageAbbrv){
return new ResponseEntity<>(languageService.addNewLanguage(languageName, languageAbbrv), HttpStatus.OK);
}
The first question is: should i use languageName and languageAbbrv as RequestParams , or should i create a new object containing these two parameter and pass it as RequestBody?
The second question: I'm letting the database generate the Id of the language, is it a good practice or not !
Related
I am trying to sort my table's content on the backend side, so I am sending org.springframework.data.domain.Pageable object to controller. It arrives correctly, but at the repository I am getting org.hibernate.hql.internal.ast.InvalidPathException. Somehow the field name I would use for sorting gets an org. package name infront of the filed name.
The Pageable object logged in the controller:
Page request [number: 0, size 10, sort: referenzNumber: DESC]
Exception in repository:
Invalid path: 'org.referenzNumber'","logger_name":"org.hibernate.hql.internal.ast.ErrorTracker","thread_name":"http-nio-8080-exec-2","level":"ERROR","level_value":40000,"stack_trace":"org.hibernate.hql.internal.ast.InvalidPathException: Invalid path: 'org.referenzNumber'\n\tat org.hibernate.hql.internal.ast.util.LiteralProcessor.lookupConstant(LiteralProcessor.java:111)
My controller endpoint:
#GetMapping(value = "/get-orders", params = { "page", "size" }, produces = { MediaType.APPLICATION_JSON_VALUE })
public ResponseEntity<PagedModel<KryptoOrder>> getOrders(
#ApiParam(name = "searchrequest", required = true) #Validated final OrderSearchRequest orderSearchRequest,
#PageableDefault(size = 500) final Pageable pageable, final BindingResult bindingResult,
final PagedResourcesAssembler<OrderVo> pagedResourcesAssembler) {
if (bindingResult.hasErrors()) {
return ResponseEntity.badRequest().build();
}
PagedModel<Order> orderPage = PagedModel.empty();
try {
var orderVoPage = orderPort.processOrderSearch(resourceMapper.toOrderSearchRequestVo(orderSearchRequest), pageable);
orderPage = pagedResourcesAssembler.toModel(orderVoPage, orderAssembler);
} catch (MissingRequiredField m) {
log.warn(RESPONSE_MISSING_REQUIRED_FIELD, m);
return ResponseEntity.badRequest().build();
}
return ResponseEntity.ok(orderPage);
}
the repository:
#Repository
public interface OrderRepository extends JpaRepository<Order, UUID> {
static final String SEARCH_ORDER = "SELECT o" //
+ " FROM Order o " //
+ " WHERE (cast(:partnerernumber as org.hibernate.type.IntegerType) is null or o.tradeBasis.account.retailpartner.partnerbank.partnerernumber = :partnerernumber)"
+ " and (cast(:accountnumber as org.hibernate.type.BigDecimalType) is null or o.tradeBasis.account.accountnumber = :accountnumber)"
+ " and (cast(:orderReference as org.hibernate.type.LongType) is null or o.tradeBasis.referenceNumber = :orderReference)"
+ " and (cast(:orderReferenceExtern as org.hibernate.type.StringType) is null or o.tradeBasis.kundenreferenceExternesFrontend = :orderReferenceExtern)"
+ " and (cast(:dateFrom as org.hibernate.type.DateType) is null or o.tradeBasis.timestamp > :dateFrom) "
+ " and (cast(:dateTo as org.hibernate.type.DateType) is null or o.tradeBasis.timestamp < :dateTo) ";
#Query(SEARCH_ORDER)
Page<Order> searchOrder(#Param("partnerernumber") Integer partnerernumber,
#Param("accountnumber") BigDecimal accountnumber, #Param("orderReference") Long orderReference,
#Param("orderReferenceExtern") String orderReferenceExtern, #Param("dateFrom") LocalDateTime dateFrom,
#Param("dateTo") LocalDateTime dateTo, Pageable pageable);
}
Update:
I removed the parameters from the sql query, and put them back one by one to see where it goes sideways. It seems as soon as the dates are involved the wierd "org." appears too.
Update 2:
If I change cast(:dateTo as org.hibernate.type.DateType) to cast(:dateFrom as date) then it appends the filed name with date. instead of org..
Thanks in advance for the help
My guess is, Spring Data is confused by the query you are using and can't properly append the order by clause to it. I would recommend you to use a Specification instead for your various filters. That will not only improve the performance of your queries because the database can better optimize queries, but will also make use of the JPA Criteria API behind the scenes, which requires no work from Spring Data to apply an order by specification.
Since your entity Order is named as the order by clause of HQL/SQL, my guess is that Spring Data tries to do something stupid with the string to determine the alias of the root entity.
How do I make swagger-ui to show pre-populated real values in the Example box below? (The box for request model that shows when user clicks on "Try it out" button in swagger-ui).
I am using SpringBoot/Java and I would like it to show some real values rather than data types. It seam to do that for DOB field by default.
I have MyModel defined as below and I was expecting that using "value" in ApiModelProperty will set these values but it is not:
#ApiModel(description="blahblah")
public class MyModel {
#ApiModelProperty(notes = "some notes", name = "name", required = true, value = "Bot")
private String name;
#ApiModelProperty(notes = "Date of birth", name = "dob", required = true)
private OffsetDateTime dob;
#ApiModelProperty(notes="How old", name = "age", required = true, value = "31")
private Integer age;
...
}
I would like above to look like:
Use example:
#ApiModelProperty(notes = "some notes", name = "name", required = true, example = "Bot")
private String name;
Is there any way that I can generate ID field as 4 digit number i.e from 1000 to 9999 in my Spring boot application. Current Id field looks like this:
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE)
#Column(name = "EMP_ID", nullable = false)
public short getEmp_id() {
return emp_id;
}
As of now id is getting generated from 1. But I wanted to get it generated starting from 1000 and incremented by 1 until 9999.
As suggest by Ishikawa in comments and by referring Sequence Generation from Sequence Generation did below changes:
#Id
#GenericGenerator(
name = "empid-sequence-generator",
strategy = "org.hibernate.id.enhanced.SequenceStyleGenerator",
parameters = {
#Parameter(name = "sequence_name", value = "user_sequence"),
#Parameter(name = "initial_value", value = "1000"),
#Parameter(name = "increment_size", value = "1")
}
)
#GeneratedValue(generator = "empid-sequence-generator")
#Column(name = "EMP_ID", nullable = false)
public short getEmp_id() {
return emp_id;
}
but even after that when trying to save the emp getting the below exception:
com.microsoft.sqlserver.jdbc.SQLServerException: Invalid object name 'user_sequence'.
at com.microsoft.sqlserver.jdbc.SQLServerException.makeFromDatabaseError(SQLServerException.java:262)
at com.microsoft.sqlserver.jdbc.SQLServerStatement.getNextResult(SQLServerStatement.java:1624)
at com.microsoft.sqlserver.jdbc.SQLServerPreparedStatement.doExecutePreparedStatement(SQLServerPreparedStatement.java:594)
at com.microsoft.sqlserver.jdbc.SQLServerPreparedStatement$PrepStmtExecCmd.doExecute(SQLServerPreparedStatement.java:524)
at com.microsoft.sqlserver.jdbc.TDSCommand.execute(IOBuffer.java:7194)
at com.microsoft.sqlserver.jdbc.SQLServerConnection.executeCommand(SQLServerConnection.java:2979)
at com.microsoft.sqlserver.jdbc.SQLServerStatement.executeCommand(SQLServerStatement.java:248)
at com.microsoft.sqlserver.jdbc.SQLServerStatement.executeStatement(SQLServerStatement.java:223)
NOTE: It's third party database so I can't do any schema/constraint changes.I need to handle this through java code only.
My bad. Forgot to uncomment below line in application.properties.
spring.jpa.hibernate.ddl-auto = update
After uncommenting when I reboot my application it created the "user_sequence".
I have two tables that are connected via class name (1:n).
Domain: Product (1)
Domain: HistoryPrice (n)
Product
#Entity
#Table
public class Product extends AbstractBaseDomain<Long> {
#NotBlank
#Size(min = 2, max = 50)
#Column(name ="name", unique = true)
private String name;
HistoryPrice
#Entity
#Table(name = "historyPrice")
public class HistoryPrice extends AbstractBaseDomain<Long> {
#NotNull
#ManyToOne
#JoinColumn(name ="product")
private Product product;
This is my repository
#Repository
public interface HistoryPriceRepository extends JpaRepository<HistoryPrice, Long> {
#Query(value = "SELECT h.product " +
"FROM history_price h " +
"INNER JOIN product p ON h.product = p.name " +
"WHERE p.name = :name", nativeQuery = true)
List<?> findProductByName(#Param("name") String name);
}
This is my Controller
#PostMapping(value = "/historyPrice")
public String searchForProducts(Model model, #RequestParam String namePart) {
List<?> productList = historyPriceService.findProductName(namePart);
model.addAttribute(HISTORYPRICE_VIEW, productList);
return HISTORYPRICE_VIEW;
}
This is my SQL output of my table creation:
2019-04-11 18:39:20 DEBUG org.hibernate.SQL - create table history_price (id bigint not null, version integer, price decimal(19,2) not null, valid_since timestamp not null, product bigint not null, primary key (id))
2019-04-11 18:39:20 DEBUG org.hibernate.SQL - create table product (id bigint not null, version integer, current_price decimal(19,2) not null, manufacturer varchar(50), name varchar(50), primary key (id))
This is my shortened error that I always get:
Caused by: org.hibernate.exception.DataException: could not extract ResultSet
Caused by: org.h2.jdbc.JdbcSQLException: Datenumwandlungsfehler beim Umwandeln von "HAMMER"
Data conversion error converting "HAMMER"; SQL statement:
SELECT h.product FROM history_price h INNER JOIN product p ON h.product = p.name WHERE p.name = ? [22018-197]
at org.h2.message.DbException.getJdbcSQLException(DbException.java:357)
at org.h2.message.DbException.get(DbException.java:168)
Caused by: java.lang.NumberFormatException: For input string: "HAMMER"
at java.base/java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
I do not know whether my problem is in my repository or somewhere else.
Maybe someone can give me a the right solution or a good hint.
Thank you very much.
The problem indicated by your stacktrace is your join. You try to join h.product which is the id of the product object internally to h.product.name which is a string. Spring tries to parse the string as number afterwards thus resulting in the NumberFormatException.
I assume you want to get the HistoryPrice objects. Thus you have three options in your repository:
Use native query as you do now but fix tablenames and join, I assume this could work:
"SELECT h.* " +
"FROM historyPrice h " +
"INNER JOIN product p ON h.product = p.id " +
"WHERE p.name = :name"
Use a JPQL query:
"SELECT h " +
"FROM historyPrice h " +
"INNER JOIN product p " +
"WHERE p.name = :name"
Use the method name to let spring data generate your queries:
List<HistoryPrice> findAllByProductName(String name);
do you have any stack ?
Can you copy the error ?
In your log stack you should see some caused by label which will give you the place where the exception is throwed
It seems in the native query you are trying to equate a product object with a string name.
#Query(value = "SELECT h.product " +
"FROM history_price h " +
"INNER JOIN product p ON h.product.name = p.name " +
"WHERE p.name = :name", nativeQuery = true)
List<?> findProductByName(#Param("name") String name);
If the Product Entity contains a name variable, then the above query might execute.
I am a JPA newbie and wanted to have a JPA native query for a single table (below) which I would like to fetch in my #Entity based class called TestRequest. It has a column 'RequestTime' that is fetched with DAYNAME() and then with DATEDIFF() functions.
SELECT TestRequest.Id AS Id
, TestRequest.RequestTime AS RequestTime
, DAYNAME(TestRequest.RequestTime) AS RequestDay
, TestRequest.StatusMessage AS StatusMessage
, DATEDIFF(CURDATE(), TestRequest.RequestTime) AS HowLongAgo
FROM TestRequest
LEFT JOIN TestRun
ON TestRequest.TestRunId = TestRun.Id
WHERE Requestor = '[NAME]'
ORDER BY Id DESC
Is there any way in which the column (fetched second time as HowLongAgo) be set into a property which is not mapped to a table column within the TestRequest class? Are there any field level annotations for this?
You need to use Interface-based projections:
You will need to create an interface that define the getters for each field in your projection like:
public interface RequestJoinRunProjection {
int getId();
LocalDate getRequestTime();
String getMessage();
String getRequestDay();
Long getHowLongAgo();
}
Then you define a method on your Repository that has the native query you want to run:
public interface TestRequestRepository extends CrudRepository<TestRequest, Long> {
// Any other custom method for TestRequest entity
#Query(value = "SELECT trq.Id AS id " +
" , trq.RequestTime AS requestTime " +
" , DAYNAME(trq.RequestTime) AS requestDay " +
" , trq.StatusMessage AS statusMessage " +
" , DATEDIFF(YEAR, CURDATE(), trq.RequestTime) AS howLongAgo " +
"FROM TestRequest trq " +
" LEFT JOIN TestRun tr " +
" ON trq.TestRunId = tr.Id " +
"WHERE Requestor = ?1 ORDER BY Id DESC"), nativeQuery = true)
List<RequestJoinRunProjection> findTestSumary(String name);
}
Notice query must be native since you are using database functions, also the column names must match the setters of your projection interface(following bean rules), so use AS in order to change the names in your query.
I strongly suggest you test your query on h2 before injecting into #Query annotation. DATEDIFF requires 3 parameters.