#Converter() is ignored when I am using it in abstract class - spring

I have problem that #Converter() is ignored when I am using it in abstract class.
#Entity
#Table(name = "CHALLENGE")
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
public abstract class AbstractChallenge implements SecretChallenge {
...
#Convert(converter = InstantConverter.class)
Instant creationTime;
...
}
If I use it in standard JPA it works as expected.
#Entity
public class PasswordResetTokenJPA {
...
#Convert(converter = InstantConverter.class)
private Instant issuedAt;
...
}
Here is my converter:
#Converter(autoApply = true)
public class InstantConverter implements AttributeConverter<Instant, Timestamp> {
#Override
public Timestamp convertToDatabaseColumn(Instant instant) {
if (instant == null) return null;
return new Timestamp(instant.toEpochMilli());
}
#Override
public Instant convertToEntityAttribute(Timestamp value) {
if (value == null) return null;
return value.toInstant();
}
}

Related

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)));
}
}
}

Spring JPA Transaction ID

I have added an attribute to all my entities - transaction id - which is a sequence generated value that I bump up once in each transaction.
I also store the transaction id with user and start/end times so I have an audit trail for every change in the database.
What is the best way to handle storing a complete graph, where I basically only want to apply the transaction id to those entities that are actually dirty?
I can put a #PrePersist and #PreUpdate on the transaction id column, but how do I retrieve the value for the current transaction id? Is there a way to store and retrieve a value on the transaction object or other JPA controller? Do I need to use a ThreadLocal solution?
Ok, here is what I did. It seems to work in all of the use cases, though I have not done any performance testing, etc. If anyone sees anything that may be non-optimal or may fail in certain situations, please point it out.
Here is the base service class that all #Service implementations must extend:
public class BaseService
{
private final ActivityService activityService;
private final ApplicationEventPublisher applicationEventPublisher;
public static ThreadLocal<Activity> transaction = new ThreadLocal<>();
public BaseService(ActivityService activityService, ApplicationEventPublisher applicationEventPublisher)
{
this.activityService = activityService;
this.applicationEventPublisher = applicationEventPublisher;
}
Object executeWithinActivity(Updater updater)
{
boolean startedLocally = false;
try
{
if (transaction.get() == null)
{
startedLocally = true;
Activity activity = activityService.startTransaction();
transaction.set(activity);
}
return updater.execute(transaction.get());
}
finally
{
if (startedLocally)
{
applicationEventPublisher.publishEvent(new TransactionEvent());
Activity activity = transaction.get();
activityService.endTransaction(activity);
}
}
}
protected interface Updater
{
Object execute (Activity activity);
}
static class TransactionEvent
{
}
}
Activity is the entity that represents the stored transaction id:
#Entity
#Getter #Setter
#Table(name = "transactions", schema = "public", catalog = "euamdb")
public class Activity
{
#Id
#Column(name = "transaction_id", nullable = false)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "tx_generator")
#SequenceGenerator(name = "tx_generator", sequenceName = "transaction_seq", allocationSize = 1)
private long transactionId;
#Basic
#Column(name = "user_id", length = 24)
private String userId;
#Basic
#Column(name = "transaction_start")
#CreationTimestamp
private Date transactionStart;
#Basic
#Column(name = "transaction_end")
#UpdateTimestamp
private Date transactionEnd;
#Override
public boolean equals(Object o)
{
if (this == o) return true;
if (!(o instanceof Activity)) return false;
Activity that = (Activity) o;
return transactionId == that.transactionId;
}
#Override
public int hashCode()
{
return Long.hashCode(transactionId);
}
}
ActivityService (which does not extend BaseService):
#Service
public class ActivityService
{
private final ActivityRepository activityRepository;
private final AuthUserService authService;
#Autowired
public ActivityService(ActivityRepository activityRepository, AuthUserService authService)
{
this.activityRepository = activityRepository;
this.authService = authService;
}
#Transactional
public Activity startTransaction()
{
Activity activity = new Activity();
activity.setTransactionStart(new Date());
activity.setUserId(authService.getAuthenticatedUserId());
activityRepository.save(activity);
return activity;
}
#Transactional
public void endTransaction(Activity activity)
{
activity.setTransactionEnd(new Date());
activityRepository.save(activity);
}
}
The base entity class for all entities (excepting Activity):
#MappedSuperclass
#Getter #Setter
public class BaseEntity
{
#Basic
#Column(name = "transaction_id")
private Long transactionId;
#PrePersist
#PreUpdate
public void setupTransaction ()
{
ThreadLocal<Activity> transaction = BaseService.transaction;
Activity activity = transaction.get();
long transactionId = activity.getTransactionId();
setTransactionId(transactionId);
}
}
An example of a service:
#Service
public class OrganizationService extends BaseService
{
private final OrgUserRepository orgUserRepository;
private final UserService userService;
#Autowired
public OrganizationService(ActivityService activityService,
OrgUserRepository orgUserRepository,
UserService userService,
ApplicationEventPublisher applicationEventPublisher)
{
super(activityService, applicationEventPublisher);
this.orgUserRepository = orgUserRepository;
this.userService = userService;
}
#Transactional
public OrgUser save(User user, OrgUser orgUser)
{
return (OrgUser) executeWithinActivity(activity ->
{
orgUser.setUser(userService.save(user));
return orgUserRepository.save(orgUser);
});
}
}
UserService also will extend BaseService and the save(OrgUser) method will also executeWithinActivity.
Finally, the commit listener:
#Component
public class AfterCommitListener
{
#TransactionalEventListener(phase = TransactionPhase.AFTER_COMPLETION)
public void doAfterTxComplete(BaseService.TransactionEvent event)
{
BaseService.transaction.remove();
}
}

Spring Data postgresql 10 insertion does not work

I am working on spring boot application with RestController, Service a Repository and an Entity.
My problem is when I call the web service to save my data in the data base, it seems it works fine and there is no exception thrown but when I check my data base I find that the table was created but I find no data saved. and here is what I get in the output(for each element in my list):
Hibernate:
insert
into
table_name
(columnOne, columnTwo)
values
(?, ?)
Here is my code:
RestController:
#RestController
#RequestMapping(path = "/api/")
public class myController {
#Autowired
private MyService myService;
#PostMapping(path="/inject/{year}")
public void myControllerMethod(#PathParam("year") Year year) {
this.myService.myServiceMethod(year);
}
}
Service:
#Service
public class MyService {
#Autowired
MyRepository myRepository;
public void myServiceMethod(Year year) {
List<MyEntity> myEntityList = this.parseMyEntityList(year);
this.myRepository.save(myEntityList)
}
}
Repository:
#Repository
public interface MyRepository extends CrudRepository<MyEntity, Long>, JpaSpecificationExecutor<InseeLibelle> {
}
Entity:
#Entity
#Table(name = "table_name", indexes = {
#Index(name = "columnOne_idx", columnList = "columnOne"),
#Index(name = "columneTwo_idx", columnList = "columnTwo"),
})
public class MyEntity{
#JsonIgnore
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long columnId;
#Column
private Integer columnOne;
#Column
private String columnTwo;
public Integer getColumnOne() {
return columnOne;
}
public void setColumnOne(Integer columnOne) {
this.columneOne = colmunOne;
}
public String getColumnTwo() {
return columnTwo;
}
public void setColumnTwo(String columnTwo) {
this.columnTwo = columnTwo;
}
}
I tried to add this line in the repository but it does not work too:
<S extends MyEntity> Iterable<S> save(Iterable<S> entities) ;
Perhaps the problem is with the pgAdmin (like my case), it does not show the data but they exist in the database, try findAll method in the repository or check them with select * directly.

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).

Duplicate creation of JPA entity

I'm quite confused, I'm trying to find out why I'm getting the creation of two Customers in the database with the following code with no luck. I tried to cut all the noise from the code, I hope I didn't erased anything important for the resolution of the problem.
Here are in the following order : Entities, DAOs, Services and the SpecialService with the specialTreatment which holds the bogus behavior.
In the specialTreatment the aim is to get an existing Order with no Customer linked to it, create a new Customer, and associate it to the order.
Entities :
Order.java :
#Entity
public class Order {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "pk_id_order")
private Integer id;
// Other fields ...
#BatchFetch(value = BatchFetchType.IN)
#JoinColumn(name = "fk_id_customer", referencedColumnName = "pk_id_customer")
#ManyToOne(fetch = FetchType.LAZY, optional = false)
private Customer customer;
// Other fields ...
// Getter & setters for each field
#Override
public int hashCode() {
int hash = 7;
hash = 79 * hash + (this.id != null ? this.id.hashCode() : 0);
return hash;
}
#Override
public boolean equals(Object obj) {
if (obj == null || !(obj instanceof Order)) {
return false;
}
final Order other = (Order) obj;
if (this.id != other.id && (this.id == null || !this.id.equals(other.id))) {
return false;
}
return true;
}
}
Customer.java :
#Entity
public class Customer {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "pk_id_customer")
private Integer id;
// Other fields
#OrderBy(value = "createdAt DESC")
#OneToMany(mappedBy = "customer", fetch = FetchType.LAZY)
private List<Order> orders;
#Override
public int hashCode() {
return (id != null ? id.hashCode() : 0);
}
#Override
public boolean equals(Object object) {
if (object == null || !(object instanceof Customer)) {
return false;
}
final Customer other = (Customer) object;
if ((this.id == null && other.id != null) || (this.id != null && !this.id.equals(other.id))) {
return false;
}
return true;
}
}
DAOs :
#Repository
#Transactional
public class CustomerDao {
#PersistenceContext
protected EntityManager em;
public void create(Customer customer) {
this.em.persist(customer);
}
}
#Repository
#Transactional
public class OrderDao {
#PersistenceContext
protected EntityManager em;
public Order edit(Order order) {
return this.em.merge(order);
}
}
Services :
#Service
#Transactional
public class CustomerService {
#Autowired
private CustomerDao customerDao;
public void create(Customer customer) {
this.customerDao.create(customer);
}
}
#Service
#Transactional
public class OrderService {
#Autowired
private OrderDao orderDao;
public void edit(Order order) {
this.orderDao.edit(order);
}
}
#Service
#Transactional
public class SpecialService {
#Autowired
private CustomerService customerService;
#Autowired
private OrderService orderService;
#Transactional(propagation = Propagation.REQUIRES_NEW)
public void specialTreatment(Order order) {
Customer customer = new Customer();
// Fill customer ...
this.customerService.create(customer); // LINE X
order.setCustomer(customer); // LINE Y
this.orderService.edit(order);
}
}
Note :
When commenting line X :
- There is no customer created (as expected), the order is edited as expected
When commenting line Y :
- The customer is created as expected (only one row) but it is not linked to my Order
The code is called from a Spring MVC controller
Any advice ?

Resources