I was perusing the spray-swagger library and came across this piece of code:
#ApiModel(description = "A pet object")
case class Pet(
#(ApiModelProperty #field)(value = "unique identifier for the pet")
val id: Int,
#(ApiModelProperty #field)(value = "The name of the pet")
val name: String)
I went to my trusty copy of Programming In Scala 3ed to find out what syntax of using the #field annotation within another annotation (re: #(ApiModelProperty #field)) but I came up short. I cracked open the #ApiModelProperty code and found:
/**
* Adds and manipulates data of a model property.
*/
#Target({ElementType.METHOD, ElementType.FIELD})
#Retention(RetentionPolicy.RUNTIME)
#Inherited
public #interface ApiModelProperty {
Question: Are we providing the compiler clues as to what context the #ApiModelProperty annotation applies in order to satisfy its #Target annotation?
You can find the answer in the documentation for scala.annotation.target package.
The #field meta-annotation is used to tell the compiler that the actual field named id should be considered the target of the annotation, as opposed to the automatically generated accesssor/mutator methods:
id():Int and id_=(i: Int):().
Related
What is "the kotlin way" to define JPA entity ID?
#Entity
data class User (
#Id #GeneratedValue
var id: Long? = null,
...
)
Or is there any better one to avoid nullable id?
You can use a 0 value rather than a null value.
#Entity
data class User (
#Id #GeneratedValue
var id: Long = 0,
...
)
Autogeneration should still find the next sequence.
Kotlin compiles to Java, which has both, a primitive type long and a Class Long
As per the Java Persistence Specification in section 11.1.21 Id Annotation both can be used for the Id:
The field or property to which the Id annotation is applied should be one
of the following types: any
Java primitive type; any primitive wrapper type; java.lang.String; java.util.Date;
java.sql.Date; java.math.BigDecimal; java.math.BigInteger[109].
There is an advantage in using the Class over the primitive, as null has a more unambiguous meaning. But from the spec both are possible and you have to decide weather you favor Kotlins nullsafety over the the jpa style or the other way around.
Usually, data class is useful to ruturn more that one result from a method , but not for entities (just my opinion).
Sometimes it is not a good idea to set a default value for the id field.
After some time experimenting with Kotlin and entities (actually, with documents for MongoDb, but anyway it has id),
Looks like the better way is to use lateinit var. You can create the top class of entity hierarchy:
open class Identifiable {
lateinit var id: Long // or String or UUID
//explicitly define equals & hash code here
}
But be careful, for equals, hashcode and if you want to provide toString method in heirs, then it is a good idea to provide extra nullable field, something like:
open class Identifiable {
lateinit var id: Long // or String or UUID
val nullableId: Long?
get() {
return if(this::id.isInitialized) id else null
}
//explicitly define equals & hash code here with nullableId
}
class User {
override fun toString() = "User(id=${nullableId})"
}
In this case, you will avoid an exception when you will try to log your created but not saved in DB entity
I'm a bit stuck and don't understand what's going on.
This one doesn't work
#Entity
#DynamicInsert
#DynamicUpdate
#SelectBeforeUpdate
#Table
class Entity {
#Column(nullable = false)
var owner: String = _
}
val myEntity = new Entity() {
owner = "some owner 1"
}
session.persist(myEntity)
Hibernate throws exception:
java.lang.IllegalArgumentException: Unknown entity:persistence.dao.EntityDaoTest$$anonfun$13$$anonfun$14$$anon$5
at org.hibernate.internal.SessionImpl.firePersist(SessionImpl.java:777)
This one works:
val myEntity = new Entity()
entity.owner = "some owner 1"
session.persist(myEntity)
Why? Why does hibernate don't recognize my Entity instance?
UPD:
#Sheinbergon, thanks, it's clear. I completely forgot that annotations are lost. Is there any possibility to set entity fields with some shortcut?
writing
val myEntity = new MyEntity()
myEntity.owner = "some owner"
myEntity.someOtherProperty = "value"
is super boring
One more question
This one works:
val parent = new Parent
parent.owner = "Our parent"
parent.addChild(new Child() {
name = "First parent's child"
addGrandChild(new GrandChild() {
name = "Grand child name"
addGrandGrandChild(new GrandGrandChild() {
name = "Grand Grand child name"
address = new Address() {
id = 1L
}
})
})
})
Why? Child, GrandChild, GrandGrandChild also created anonymously.
addChild, addGrandChild, addGrandGrandChild are just list mutators.
def addChild(child: Child): Unit = {
if (children == null) {
children = new util.ArrayList[Child]()
}
if (Option(child.parent).isEmpty) {
child.parent = this
}
children.add(child)
}
What you are doing here is instantiating a class anonymously in Scala , and well... that creates an anonymous implementation of your class Entity ( like instantiating an interface anonymously in Java).
you can see it by printing the class name - println(myEntity.getClass) in both cases
Annotations applied to the original class do not apply to the anonymous one (reflection can still find them in the super class, but that's up to the code scanning them) and I guess that's why you're getting the various JPA exceptions
In response to your added sub-questions
Regarding a shortcut - why don't you use companion objects for factories or turn this class into a case class (with defaults), allowing for nicer, more flexible initialization.
Regarding the second object graph(and assuming eachof your classes are annotated) - again it depends on how the reflective code treats the objects it scans. it's possible ( and more likely, given that it won't scan each member of the collection for annotations ) it takes annotation definitions from the erased type ( possible to get it's FQDN class name as ParameterizedType in Java's reflection API) of the collection and not from the actual members of the collection and that's why it works.
I'm not really sure what it does about field definitions though (they are only present in the "super" class), but there's no "magic" here, just plain old reflection scans.
I'm trying to get Kotlin working with jsr 303 validation on a spring-data-rest project.
Given the following data class declarartion :
#Entity data class User(
#Id
#GeneratedValue(strategy = javax.persistence.GenerationType.AUTO)
var id: Long? = null,
#Size(min=5, max=15)
val name: String
)
The #Size annotation has no effect here, making me able to save a user with a name of 1 character.
It works well when executing the very same example but in a Java class instead of Kotlin.
This makes me think of a Kotlin problem.
Thanks in advance for you help !
You need to use Annotation use-site targets since the default for a property declared in the constructor is to target the annotation on the constructor parameter instead of the getter (which will be seen by JavaBeans compliant hosts) when there are multiple options available. Also using a data class might be inappropriate here (see note at end).
#Entity data class User(
#Id
#GeneratedValue(strategy = javax.persistence.GenerationType.AUTO)
var id: Long? = null,
#get:Size(min=5, max=15) // added annotation use-site target here
val name: String
)
The property target from the Kotlin docs may look tempting, but it can only be seen from Kotlin and not Java. Usually get does the trick, and it is not needed on the bean set.
The docs describe the process as:
If you don’t specify a use-site target, the target is chosen according to the #Target annotation of the annotation being used. If there are multiple applicable targets, the first applicable target from the following list is used:
param
property
field
And the #Size annotation is:
#Target(value={METHOD,FIELD,ANNOTATION_TYPE,CONSTRUCTOR,PARAMETER})
Therefore since PARAMETER is a valid target, and multiple targets are available (parameter, field, method [get/set]) it choses PARAMETER which is not what you want. Therefore for a JavaBean host to see the property it will look for the getter (properties are defined by the getter/setter and not the backing field).
In one of the Java samples, it shows:
public class Book {
private String title;
private String description;
// ...
#NotEmpty(groups={FirstLevelCheck.class, Default.class})
#Size(max=30)
public String getTitle() {
return title;
}
// ...
}
Which matches our usage of having it on the getter. If it were to be on the field like some of the validation annotations show, see the field use-site target. Or if the field must also be publicly accessible, see the #JvmField annotation in Kotlin.
NOTE: As mentioned in notes from others, you should likely consider NOT using a data class for entities if they use an auto-generated ID since it will not exist for new objects the same as for retrieved objects; and a data class will generate equals and hashCode to include all fields including the ones it should not. You can read guidance about this from the Hibernate docs.
Use the #get or #field targets for validation annotations. Annotations with the target #param(first default) and #property are not supported.
e.g:
From #NotEmpty To #field:NotEmpty
data class Student(
#field:NotEmpty #field:Size(min= 2, message = "Invalid field") var name: String? = ""
)
GL
Jayson Minard
Annotation use site targets
I met a very strange phenomenon when using dozer in jpa project.
I have a UserSupplier object and a Supplier object.
UserSupplier:
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "supplier_id", nullable = false)
private Supplier supplier;
In my code I first query a UserSupplier List, then convert it to SupplierList.
List<Supplier> supplierList = new ArrayList<>(usList.size());
usList.forEach(us -> supplierList.add(us.getSupplier()));
Then I convert SupplierList to SupplierView List and return it to Caller.
BeanMapper.mapList(supplierList, SupplierView.class);
My dozer configure in these objects like below
Supplier:
#Id
#GeneratedValue
#Mapping("supplierId")
private int id;
SupplierView:
private int supplierId;
Very funny, supplierId in SupplierView always 0(default int value),but other fileds can convert successfully, only id field fail. I don't why is this, why only id field can't convert to supplierId, but other fields could?
For above problem, there are below solutions
1. Change field name (supplierId to id):
Supplier:
// #Mapping("supplierId")
private int id;
SupplierView:
private int id;
but Caller(front-end) have to change code.
2. Change fetchType to eager:
UserSupplier:
#ManyToOne
private Supplier supplier;
After reading dozer documentation, I find some thing. After trying it, I got another solution.
That is add a dozer.properties into classpath, content inside is
org.dozer.util.DozerProxyResolver=org.dozer.util.HibernateProxyResolver
More detail please see
http://dozer.sourceforge.net/documentation/proxyhandling.html
This is probably because JPA uses proxy objects for lazy loading of single entity reference. Proxy object is effectively a subclass of your entity class. I guess that dozer can find #Mapping annotation only on fields declared in the class of given object, and not on fields defined in parent classes. Dozer project states that annotation mapping is experimental. Therefore it is possible that it does not cover mapping class hierarchies well.
I suggest to try configure mapping of supplierId by other means (XML, dozer mapping API) and see if it works. If all fails, you could write a custom MapperAware converter between Supplier and SupplierView. You would map source object to target object using supplied mapper, and finilize it by copying value of id to supplierId.
I read many question in this forum but nothing works.
public #interface MyAnnotation {
String value() default "";
Class[] exceptionList;
}
#MyAnnotation(value="hello", exceptionList={TimeOutException.class})
public void method() {}
#Aspect
public class MyAspect {
#Around("#annotation(MyAnnotation)")
public Object handle(ProceedingJoinPoint joinPoint, MyAnnotation myAnnotation) {
System.out.println(myAnnotation.exceptionList); // should print out TimeOutException
}
}
How can I get the value and the exceptionList of the #MyAnnotation while executing the advice?
I'm using Spring 4.0.6, AspectJ 1.7.4
The solution for this is making sure the advice method's parameter name match the parameter name in AspectJ expression. In my case, the advice method should look like this:
#Aspect
public class MyAspect {
#Around("#annotation(myAnnotation)")
public Object handle(ProceedingJoinPoint joinPoint, MyAnnotation myAnnotation) {
System.out.println(myAnnotation.exceptionList); // should print out TimeOutException
}
}
You are already almost there. Probably.
You are using the correct way to retrieve the annotation, so you have the values available.
Your problem - if I interpret the very minimalistic problem description(!) you only provide via the comment in your code snippet(!) correctly - is the (wrong) assumption that sticking an array of the type Class into System.out.println() will print out the names of the Classes it contains. It does not. Instead it prints information about the reference:
[Ljava.lang.Class;#15db9742
If you want the names of the Classes, you will have to iterate over the elements of that array and use .getName(), .getSimpleName() or one of the other name providing methods of Class.
Further information on how to print elements of an array is here:
What's the simplest way to print a Java array?
Granted, this whole answer could be entirely besides the point if the problem is that you are getting null values from the annotation fields. But since you have not provided an adequate problem description ("nothing works" is not a problem description!), we can only guess at what your problem is.