I'm creating a Grails 2.4.3 app and have the following Domain classes:
class StockItem {
String name
BigDecimal wholesalePrice
BigDecimal retailPrice
BigDecimal profit
static belongsTo = [ledger: Ledger]
static constraints = {
name minSize:3, maxSize:255
wholesalePrice min:0.0, scale:2
retailPrice min:0.0, scale:2
retailPrice validator: { retailPrice, StockItem obj ->
if (retailPrice < obj.wholesalePrice) {
['retailLessThanWholesale']
}
}
}
static mapping = {
profit(formula: 'RETAIL_PRICE - WHOLESALE_PRICE')
}
}
class Ledger {
String name
static hasMany = [clients: Client, invoices: Invoice, availableItems: StockItem, payments: Payment]
static constraints = {
name unique:true
}
}
I have a unit test:
#Domain(StockItem)
#TestMixin(HibernateTestMixin)
class StockItemSpec extends Specification {
void "Test profit calculation"() {
when: "a stock item exists"
StockItem i = new StockItem(name: "pencil", wholesalePrice: 1.50, retailPrice: 2.75)
then: "profit is calculated correctly"
i.profit == 1.25
}
}
that is failing thusly:
Failure: Test profit calculation(com.waldoware.invoicer.StockItemSpec)
| org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'sessionFactory': Invocation of init method failed; nested exception is
org.hibernate.MappingException: An association from the table stock_item refers to an unmapped class: com.waldoware.invoicer.Ledger
The app seems to be functioning OK, and I can't understand why this test is failing. Any ideas are appreciated!
Since a belongsTo association exists between your StockItem and Ledger domain classes, you will need to add Ledger to the #Domain annotation in your test:
#Domain([StockItem, Ledger])
This should properly configure the required domain classes when the unit test runtime is initialized. Depending on your other test cases, you may also need to include Client or Payment in the annotation.
Related
First of all, I'm trying to prevent recursion in JSON, when I'm getting User entity. To do this I've added #JsonIdentityInfo annotation to my User class. It works as expected for serialization (when I get User), but on deserialization (on registering User) Jackson returns this:
Resolved [org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: No Object Id found for an instance of `.entity.User`, to assign to property 'id'; nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException: No Object Id found for an instance of `.entity.User`, to assign to property 'id'
at [Source: (PushbackInputStream); line: 13, column: 1]]
User.kt
#Entity
#Table(name = "user")
#Audited
#EntityListeners(AuditingEntityListener::class)
#Validated
#JsonInclude(JsonInclude.Include.NON_NULL)
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator::class, property = "id", scope = Int::class)
data class User(
#field:Id
#field:GeneratedValue(strategy = GenerationType.IDENTITY)
var id: Int,
#Valid
#Size(
min = 4,
message = "Username '\${validatedValue}' should be at least {min} characters long"
)
#NotEmpty(message = "asd")
var username: String = "",
...
#field:OneToOne(cascade = [CascadeType.ALL], optional = true)
var userInfo: UserInfo? = null
)
JSON I'm sending for deserialization (Works when #JsonIdentityInfo is removed):
{
"username": "qwert",
"password": "tyui",
"email": "asdasd",
"phone": ""
}
Method used for registration:
#PostMapping("/users/signup")
fun addUser(#RequestBody #Valid user: User): JSendResponse<Unit> {
user.password = passwordEncoder.encode(user.password)
userService.save(user)
return JSendResponse(data = Unit)
}
UPD:
The solution to this problem was to explicitly set id field in Json because it's generated on persistence level. But I don't want to set it explicitly, because frontend doesn't know the id of the new object.
JsonIdentityInfo is used to avoid circular reference, that causes a stackoverflow. Jackson is supposed to assign an 'id' when it finds this type of references.
It seems some versions of jackson have a bug on this functionality: github.com/FasterXML/jackson-databind/issues/1367
So you should either try to change version of jackson, or do the following alternatives:
Some alternatives are:
Use the annotations JsonManagedReference and JsonBackReference on the fields that reference each other; in your case, 'user' and 'userInfo'. JsonManagedReference is the side that gets (de)serialized normally. JsonBackReference is the back part of reference and won't be (de)serialized.
Make sure to set the 'back reference' part after deserialized.
If, for example, your UserInfo property is the managed ref, inside the User class, you should have the following results:
class User {
Long id;
#JsonManagedReference
UserInfo userInfo;
}
class UserInfo {
String nickname;
String moto;
#JsonBackReference
User user;
...
}
// OUTPUT EXAMPLE
public static void main(String[] args) throws JsonProcessingException {
UserInfo info = new UserInfo("johndoe","it works", null);
User user = new User(1L, info);
info.setUser(user);
ObjectMapper om = new ObjectMapper();
System.out.println("user = " + om.writeValueAsString(user));
System.out.println("userinfo = " + om.writeValueAsString(info));
}
OUTPUT
user = {
"id":1,
"userInfo":{
"nickname":"johndoe",
"moto":"it works"
}
}
userinfo = {
"nickname":"johndoe",
"moto":"it works"
}
Another alternatice is to Create a custom serializer and a custom deserializer for your entities. This is the most powerful way to (de)serialize; you can have both sides (de)serialized and even add custom properties.
#JsonDeserialize(using = UserDeserializer.class)
#JsonSerialize(using = UserSerializer.class)
data class User(
...
)
To create custom (de)serializers, you can follow these tutorials:
https://www.baeldung.com/jackson-deserialization
https://www.baeldung.com/jackson-custom-serialization
For anyone searching:
This issue may be caused when combining #JsonIdentityInfo.
In case you are using #JsonTypeInfo:
use #JsonTypeName together with #JsonSubTypes
wrap potential generic fields into a class extending from it's type
If you are using non-empty constructors:
try using (private) empty ones annotated with #JsonCreator (especially in referenced classes) and assign any value to final attributes (which jackson will override nevertheless)
Jackson throws very unrelated and illogical exceptions that are caused trying to deserialize an instance inside of the exact same instance, seeing them as different objects.
Tested in java, but shouldn't differ
I'm trying to build a simple application with Quarkus. Currently, I have two entity classes, which are related one-to-many:
#Entity
public class Person extends PanacheEntity {
public String name;
public LocalDate birthdate;
#OneToMany(mappedBy = "person")
public List<Address> addresses;
public static Person findByNameFirst(String name) {
return find("name", name).firstResult();
}
}
#Entity
public class Address extends PanacheEntity {
public String street;
...etc...
#ManyToOne
public Person person;
}
These are used by a simple REST webservice, which should store a Person to the database, select it again an return it:
#GET
#Path("storePerson")
#Produces(MediaType.APPLICATION_JSON)
#Transactional
public Person storePerson(
#QueryParam("name")String name,
#QueryParam("birthdate")String birthdate)
{
LocalDate birth = LocalDate.parse(birthdate, DateTimeFormatter.BASIC_ISO_DATE);
Person person = new Person(name, birth);
person.persistAndFlush();
Person p2 = Person.findByNameFirst(name);
return p2;
}
When calling the webservice the first time, the result is a JSON object with the stored data, which is as expected. When called again, an internal server error is thrown:
org.hibernate.LazyInitializationException: Unable to perform requested lazy initialization [Person.addresses] - no session and settings disallow loading outside the Session
As I understand, the error is thrown because the transaction only lasts until the storePerson method ends, but the conversion to JSON is happening outside of the method.
How can I prevent this error? I have read about the hibernate parameter "enable_lazy_load_no_trans" but it seems it is not supported in Quakus' application.properties.
The idea is to use a mapper framework such as MapStruct.
We don't recommend to directly expose your entities for 2 reasons:
the issue you have,
API management in the long run: you might have to change your model and not your API or the opposite.
There is an example here: https://github.com/mapstruct/mapstruct-examples/tree/master/mapstruct-quarkus .
The Quarkus version used is a bit old but AFAICS it should still work with latest Quarkus.
You can make the error go away by using Hibernate.initialize(person.addresses), then the collection gets initialized before the transaction ends.
I want to create a REST API for Android using my yii2 fremowork blocking site and its database.
I did not need to create a table in the database, but I would only SELECT and INSERT the information in the desired style.
How can I do this in the Java Spring boot application?
I need to download the information you need
This is my application.properties file
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost/uzaart_teda?useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=
spring.jpa.hibernate.ddl-auto=update
spring.jpa.generate-ddl=true
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.temp.use_jdbc_metadata_defaults = false
My Service implement class
#Service
public class ProductsServiceimpl implements ProductsService{
#Autowired
ProductsRepository productsRepository;
#Override
public List<ProductsDto> getProducts() {
List<ProductsDto> list=new ArrayList<>();
list.add(new ProductsDto(1,2,"anvar",4,5,6,7));
list.add(new ProductsDto(1,2,"sanjar",4,5,6,7));
/*this is my need --->*/
List<Object[]> objects=productsRepository.selectProducts();
/******/
System.out.println(objects.size());
return list;
}
}
This is my Repository
public interface ProductsRepository extends JpaRepository<Object[],Long> {
#Query(value = "SELECT a.id,a.tovar_id,t.nom_sh,a.kol_ost,a.kol_in_ost, a.opt1 AS sot,a.opt1_in AS sot_in FROM s_tovar t,asos_slave a,asos WHERE a.del_flag=1 AND (asos.tur_oper=1 OR asos.tur_oper=4 OR asos.tur_oper=5) AND a.asos_id=asos.id AND a.tovar_id=t.id AND (a.kol_ost>0 OR a.kol_in_ost>0) AND asos.client_id = 4 AND (((t.nom LIKE \"%0001%\") OR (t.nom LIKE \"%0001%\"))) ORDER BY t.nom,a.srok",nativeQuery = true)
public List<Object[]> selectProducts();
}
My result.
Error message
1.
Error starting ApplicationContext. To display the conditions report re-run your application with 'debug' enabled.
2019-07-16 16:20:19.006 ERROR 7328 --- [ main] o.s.boot.SpringApplication : Application run failed
org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'controller': Unsatisfied dependency expressed through field 'productsService';
2.
[spring-beans-5.1.8.RELEASE.jar:5.1.8.RELEASE]
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:320) ~[spring-beans-5.1.8.RELEASE.jar:5.1.8.RELEASE]
3.
at com.uz.shopapi.ShopApiApplication.main(ShopApiApplication.java:10) [classes/:na]
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'productsServiceimpl':
This is wrong:
public interface ProductsRepository extends JpaRepository<Object[],Long> {
JpaRepository must be of type of class that is annotated with #Entity:
public interface ProductsRepository extends JpaRepository<Product,Long> {
like this:
#Entity
class Product {
…
}
https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#repositories.multiple-modules.annotations
You have to create entities to use Spring Data JPA, because it needs the entity metadata to create query.
After that, you can use custom object to select result into it.
public interface ProductsRepository extends JpaRepository<Products,Long> {
#Query(select new com.xyt.CustomObject(a.tovar_id,t.nom_sh ... ) from ENTITY1 a, ENTITIY2 t where ...)
public List<CustomObject> selectProducts();
}
Alternatively, you can use simple JDBC template to create sql query with your custom object mapper. In this way you do not need to create those entities.
My Controller
#GetMapping(value="/getAllDetails")
public List<PriceListEntity> getAllDetails() {
return MyRepository.getAllDetails();
}
My Repository
#Repository
public interface MyRepository extends CrudRepository<TestNativeQ, String> {
#Query( value="SELECT qplt.name price_list_name, qplab.status_code, qplab.start_date, (SELECT charge_definition_code FROM oalfsaas_repl.QP_CHARGE_DEFINITIONS_B WHERE charge_definition_id=qplab.charge_definition_id ) chargedefinitioncode "
+ "FROM PriceListEntity qplab, PriceListLineEntity qplt "
+ " WHERE qplab.price_list_id =qplt.price_list_id ", nativeQuery = false)
public List<PriceListEntity> getAllDetails();
}
Actual Result:
[{"ABC", "DEF", "15/05/2018", "XXZ"}]
Expected Result
[{name: "ABC", statuscode: "DEF", startDate: "15/05/2018", chargedefintioncode: "XXZ"}]
The Query has join with more than one table and also subquery at column level.
Your are actually doing a projection with your select which does not return any specific object but a tuple which is an array of objects you select in your query. Whatever way the JSON is made there are no names just values.
You need to create a DTO to hold the values you want to pass by names in your JSON.
A minimal example, having a simple entity like:
#Entity
#Getter
#RequiredArgsConstructor
public class TestClass {
#Id
#GeneratedValue
private Long id;
#NonNull
private String a,b,c;
}
and willing to pass -for example - only a & b there might be DTO like:
#RequiredArgsConstructor
public class TupleDto {
#NonNull
private String a,b;
}
and in your case some sort of PriceListDetailsDto
the repository might be declared like:
public interface TestClassRepository extends CrudRepository<TestClass, Long> {
#Query(value="SELECT new org.example.TupleDto(tc.a, tc.b) FROM TestClass tc")
List<TupleDto> fetchAB();
}
NOTE: in above that there is used operator new and a full path to the entity constructor.
This way Spring repository knows how to assign selected fields and when making a JSON from this DTO will result having fields with names (names in DTO).
The new operator in JPQL is just calling new in java- So any row data a,b,c can be used to construct Java object with that object's class constructor accepting same parameter amount and types (and in the same order) so liie new MyEntityObject(a,b,c).
NOTE ALSO: in this simple case the original entity could have been used as DTO if it was modified to allow null value in c and adding corresponding constructor. In your case where your tuple is constructed from many tables you need to create a DTO to hold those values.
I'm using hibernate for my web service.
I'm able to list all the records, but unable to get just one.
The table contains:
ID (VARCHAR) VALUE(BIT)
celiac 1
rate 1
suggestions 0
The error shown is:
org.springframework.web.util.NestedServletException: Request processing failed; nested exception is org.hibernate.ObjectNotFoundException: No row with the given identifier exists: [com.pfc.restaurante.models.Device#id="xxxxxx"]
org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:894)
org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:778)
javax.servlet.http.HttpServlet.service(HttpServlet.java:617)
javax.servlet.http.HttpServlet.service(HttpServlet.java:717)
And the main code:
#JsonAutoDetect
#Entity
#Table(name = "SETTINGS")
public class Settings implements Serializable{
#Id
#Column(name="ID")
private String id;
#Column(name="VALUE", nullable=false)
private boolean value;
(...)
}
//////////////////7
#Controller
#RequestMapping("/settingsService")
public class SettingsServiceController {
#Autowired
SettingsService settingsService;
#RequestMapping(value = "/{id}", method = RequestMethod.GET)
public #ResponseBody Settings find(#PathVariable("id") String id){
return settingsService.find(id);
}
(...)
}
I've read around that it could be because DB incongruence with my entity (some nullable = true when it shouldn't), but I've checked it already and there is no such a thing.
Could someone lend me a hand?
Thanks in advance!
Your error refers to an entity named 'Device' but your code shows an entity 'Settings'. Are they the same?
I've seen this error only in 2 situations:
The main entity does not exist in the DB and Session.load() is used. Use Session.get() and check for null instead.
Broken relationships. Consider this: EntityA owns a relation to EntityB. EntityB is deleted while the FK in EntityA is left untouched. So, whenever HB tries to load the link A-B the error happens. This can happen when running a normal search or even when saving/refreshing EntityA (HB needs to refresh the link as well).