Java, list sorting with reflection - java

I want to allow to sort by every field in the class, without having to write switch/ if statements.
My idea was to find the Field that matches given string value by name and then, with Stream API neatly sort. IntelliJ screamed that i need to surround it with try-catch, so it is not so neatly looking, but that's not important, as it does not work.
private List<MyEntity> getSorted(List<MyEntity> list, SearchCriteria criteria) {
Field sortByField = findFieldInListByName(getFieldList(MyEntity.class), criteria.getSortBy());
return list.stream().sorted(Comparator.comparing(entity-> {
try {
return (MyEntity) sortByField.get(entity);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return entity;
})).collect(Collectors.toList());
}
In the MyEntity class I have added Comparable interface, but I am not sure what should be in the body of Compare(), as I dont want to specify how to compare objects, because it will change based on the selected sorting.
EDIT: Added Entity below:
#Data
#Entity
#Table(name = "role_management", schema = "mdr")
#Builder
#NoArgsConstructor
#AllArgsConstructor
#EqualsAndHashCode
public class MyEntity implements Comparable{
#Id
#Column(name = "uuid", unique = true, insertable = false, updatable = false)
#GeneratedValue(strategy = GenerationType.AUTO)
private UUID uuid;
#ManyToOne
#JoinColumn(name = "user_id", nullable = false)
private UserEntity user;
#Basic
#NonNull
#Column(name = "role")
private String role;
#Basic
#Column(name = "action")
#Enumerated(EnumType.STRING)
private RoleAction action;
#Basic
#Column(name = "goal")
#Enumerated(EnumType.STRING)
private RoleGoal goal;
#Column(name = "date")
private LocalDateTime date;
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "reporter_id", referencedColumnName = "uuid")
private UserEntity reporter;
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "authorizer_id", referencedColumnName = "uuid")
private UserEntity authorizer;
#Basic
#Column(name = "ezd")
private String ezd;
#Basic
#Column(name = "is_last")
private boolean isMostRecent;
#Override
public int compareTo(Object o) {
return 0;
}
}
EDIT 2: My code based on the #Sweeper solution:
UserEntity (nullable)
#Override
public int compareTo(UserEntity other) {
if (other == null) {
return 1;
}
return this.getMail().compareTo(other.getMail());
}
Comparator:
public static Comparator getSortComparator(Field sortByField) {
return Comparator.nullsLast(Comparator.comparing(entity -> {
try {
Object fieldValue = sortByField.get(entity);
if (!(fieldValue instanceof Comparable<?>) && fieldValue != null) {
throw new IllegalArgumentException("...");
}
return (Comparable) fieldValue;
} catch (IllegalAccessException e) {
throw new MdrCommonException(e.getLocalizedMessage());
}
}));
}

MyEntity should not implement Comparable. It is the fields, by which you are going to sort the list of MyEntity objects, that needs to be Comparable. For example, if you are sorting by the field user, which is a UserEntity, then UserEntity is the thing that needs to be comparable, not MyEntity.
The lambda's job should just be to check that the fields are indeed Comparable, and throw an exception if they are not.
Since you don't know the types of the fields at compile time, however, you'd have to use a raw type here. The comparing call would look like this:
Comparator.comparing(entity -> {
try {
Object fieldValue = sortByField.get(entity);
// This check still passes if the type of fieldValue implements Comparable<U>,
// where U is an unrelated type from the type of fieldValue, but this is the
// best we can do here, since we don't know the type of field at compile time
if (!(fieldValue instanceof Comparable<?>) && fieldValue != null) {
throw new IllegalArgumentException("Field is not comparable!");
}
return (Comparable)fieldValue;
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
})

You can create automatically comparators for any field of any class using reflection but is better create specific comparators (will be typechecked).
Your entity is a normal class with normal fields then, the usual Java sorting machinery should do the job:
Basically, if you define one comparator for every field (even deep fields into your entity):
public final static Comparator<MyEntity> ByField1 = comparing(MyEntity::getField1);
public final static Comparator<MyEntity> ByField2 = comparing(MyEntity::getField2);
public final static Comparator<MyEntity> ByField3 = comparing(MyEntity::getField3);
public final static Comparator<MyEntity> ByDeep1 = comparing(a -> a.getField4().getDeep1());
public final static Comparator<MyEntity> ByDeep2 = comparing(a -> a.getField4().getDeep2());
public final static Comparator<MyEntity> ByDeep3 = comparing(a -> a.getField4().getDeep3());
You can sort using complex sorting expressions:
data.stream()
.sorted(ByField2.reversed().thenComparing(ByDeep2))
.forEach(System.out::println);
a full example could be
public static void main(String[] args) {
List<MyEntity> data =
Stream.of("Row1", "Row2").flatMap(field1 ->
Stream.of(101, 102).flatMap(field2 ->
Stream.of(true, false).flatMap(field3 ->
Stream.of("Row1", "Row2").flatMap(deep1 ->
Stream.of(101, 102).flatMap(deep2 ->
Stream.of(true, false).map(deep3 ->
new MyEntity(field1, field2, field3, new MyDeepField(deep1, deep2, deep3))))))))
.collect(toList());
data.stream()
.sorted(ByField2.reversed().thenComparing(ByDeep2))
.forEach(System.out::println);
}
#Getter
#Setter
#AllArgsConstructor
static class MyDeepField {
private String deep1;
private Integer deep2;
private Boolean deep3;
}
#Getter
#Setter
#AllArgsConstructor
static class MyEntity {
private String field1;
private Integer field2;
private Boolean field3;
private MyDeepField field4;
public final static Comparator<MyEntity> ByField1 = comparing(MyEntity::getField1);
public final static Comparator<MyEntity> ByField2 = comparing(MyEntity::getField2);
public final static Comparator<MyEntity> ByField3 = comparing(MyEntity::getField3);
public final static Comparator<MyEntity> ByDeep1 = comparing(a -> a.getField4().getDeep1());
public final static Comparator<MyEntity> ByDeep2 = comparing(a -> a.getField4().getDeep2());
public final static Comparator<MyEntity> ByDeep3 = comparing(a -> a.getField4().getDeep3());
#Override
public String toString() {
return "MyEntity{" +
"field1='" + field1 + '\'' +
", field2=" + field2 +
", field3=" + field3 +
", deep1=" + field4.getDeep1() +
", deep2=" + field4.getDeep2() +
", deep3=" + field4.getDeep3() +
'}';
}
}
with output
MyEntity{field1='Row1', field2=102, field3=true, deep1=Row1, deep2=101, deep3=true}
MyEntity{field1='Row1', field2=102, field3=true, deep1=Row1, deep2=101, deep3=false}
...
MyEntity{field1='Row2', field2=101, field3=false, deep1=Row2, deep2=102, deep3=true}
MyEntity{field1='Row2', field2=101, field3=false, deep1=Row2, deep2=102, deep3=false}
The criteria field into your SearchCriteria class is some field of type Comparator<MyEntity> or a mapping using an enumeration or parsing string expressions or so...

Related

FindById() with a composite key

I have a composite primary key made of planId and planDate, when the user gives me both this attributes I cant find a way to retrieve it from my Repo. Should findById work like this?
public Plans assignPlansToMeds(Long id, Long planId, Date planDate) {
Set<Meds> medsSet = null;
Meds meds = medsRepo.findById(id).get();
Plans plans = plansRepo.findById(planId, planDate).get();
medsSet = plans.getAssignedMeds();
medsSet.add(meds);
plans.setAssignedMeds(medsSet);
return plansRepo.save(plans);
}
My Primary Key:
#Data
#AllArgsConstructor
#NoArgsConstructor
#EqualsAndHashCode
#Embeddable
public class PlansPKId implements Serializable {
private long planId;
private Date planDate; // format: yyyy-mm-dd
}
Plans entity:
#Data
#AllArgsConstructor
#NoArgsConstructor
#Entity
#Table(name = "plans")
public class Plans {
#EmbeddedId
private PlansPKId plansPKId;
#Column
private String planName;
#Column
private String weekday;
#ManyToMany
#JoinTable(name = "Plan_Meds", joinColumns = {
#JoinColumn(name = "planDate", referencedColumnName = "planDate"),
#JoinColumn(name = "planId", referencedColumnName = "planId") }, inverseJoinColumns = #JoinColumn(name = "id"))
private Set<Meds> assignedMeds = new HashSet<>();
}
where I ask for the planId and planDate:
#PutMapping("/medicine/{id}/assignToPlan/{planId}/date/{plandate}")
public Plans assignMedToPlan(#PathVariable Long id, #PathVariable Long planId, #PathVariable Date planDate){
return assignService.assignPlansToMeds(id, planId, planDate);
}
The Spring JpaRepository only allows one type as the ID-type, as can be seen in the javadoc. Therefore, findById will never accept two arguments.
You need to define your repository with your EmbeddedId-type as ID-type as follows:
#Repository
public interface PlansRepository extends JpaRepository<Plans, PlansPKId> {
}
You can then call the findById method as follows:
Plans plans = plansRepo.findById(new PlansPKId(planId, planDate))
.orElseThrow(PlansNotFoundException.idAndDate(planId, planDate));
If you dont want to create a new PlansPKId instance for every query, you could also define a repository method as follows and let Spring derive the query based on the method name:
Optional<Plans> findByPlansPKIdPlanIdAndPlansPKIdPlanDate(long planId, Date planDate);
If you don't like to have a cumbersome method name you could as well define a JPQL query and name the method as you like:
#Query("select p from Plans p where p.plansPKId.planId = :planId and p.plansPKId.planDate = :planDate")
Optional<Plans> findByCompositeId(#Param("planId) long planId, #Param("planDate") Date planDate);
On a side note, I strongly encourage you to use LocalDate, LocalDateTime or ZonedDateTime (depending on your needs) instead of the legacy Date class.
Moreover, you shouldn't call get() on an Optional without checking if it is present. I recently wrote an answer on SO describing how you can create an elegant error handling. If you stuck to my example, you had to create a NotFoundException and then create the PlansNotFoundException which extends NotFoundException. by this means, everytime when a PlansNotFoundException is thrown in thread started by a web request, the user would receive a 404 response and a useful message if you implement it like this:
public abstract class NotFoundException extends RuntimeException {
protected NotFoundException(final String object, final String identifierName, final Object identifier) {
super(String.format("No %s found with %s %s", object, identifierName, identifier));
}
protected NotFoundException(final String object, final Map<String, Object> identifiers) {
super(String.format("No %s found with %s", object,
identifiers.entrySet().stream()
.map(entry -> String.format("%s %s", entry.getKey(), entry.getValue()))
.collect(Collectors.joining(" and "))));
}
}
public class PlansNotFoundException extends NotFoundException {
private PlansNotFoundException(final Map<String, Object> identifiers) {
super("plans", identifiers);
}
public static Supplier<PlansNotFoundException> idAndDate(final long planId, final Date planDate) {
return () -> new PlansNotFoundException(Map.of("id", id, "date", date));
}
}
For the Meds case:
public class MedsNotFoundException extends NotFoundException {
private MedsNotFoundException(final String identifierName, final Object identifier) {
super("meds", identifierName, identifier);
}
public static Supplier<MedsNotFoundException> id(final long id) {
return () -> new MedsNotFoundException("id", id);
}
}
Meds meds = medsRepo.findById(id).orElseThrow(MedsNotFoundException.id(id));

Strongly Typed ID Autogeneratation (#EmbeddedId & #GenerateValue)

Im am mightly confused right now. What i am trying to do seems very simple. I have one Entity called 'Order' with a member 'OrderId'. The OrderId has only one property which is a long. this value is what i am trying to generate using #GeneratedValue.
As a database i am using h2 right now. Whenever i try to store something i receive the following error:
org.hibernate.id.IdentifierGenerationException: null id generated for:class xyz.Order
It's not obvous to me what i am doing wrong.
Order.java:
#Getter
#ToString
#EqualsAndHashCode
#NoArgsConstructor(access = PROTECTED)
#Entity(name = "ORDER_TABLE")
public class Order {
#EmbeddedId
private OrderNr orderNr;
#Column(name = "STATE")
private OrderState orderState;
#Embedded
private OtoId otoId;
#Column(name = "customer_wish_date")
private LocalDate customerWishDate;
private Order(Builder builder) {
checkArgument(nonNull(builder.otoId), "OTO ID must not be null");
checkArgument(nonNull(builder.customerWishDate), "CustomerWishDate must not be null");
checkArgument(builder.customerWishDate.isAfter(LocalDate.now()) || builder.customerWishDate.isEqual(LocalDate.now()), "CustomerWishDate must be today or in the future");
this.otoId = builder.otoId;
this.customerWishDate = builder.customerWishDate;
this.orderState = OrderState.ENTERED;
}
private void changeState(OrderState newState, OrderStateChangeEventPublisher changeEventPublisher) {
OrderState previousState = this.orderState;
this.orderState = newState;
changeEventPublisher.publish(this, new OrderStateTransition(previousState, this.orderState));
}
public void hold(OrderStateChangeEventPublisher changeEventPublisher) {
changeState(orderState.holding(), changeEventPublisher);
}
public void startFulfillment(OrderStateChangeEventPublisher changeEventPublisher) {
changeState(orderState.entered(), changeEventPublisher);
}
public void cancel(OrderStateChangeEventPublisher changeEventPublisher) {
changeState(orderState.canceled(), changeEventPublisher);
}
public static Builder builder() {
return new Builder();
}
public static final class Builder {
public Builder() {
}
public Order build() {
return new Order(this);
}
private OtoId otoId;
private LocalDate customerWishDate;
public Builder otoId(OtoId otoId) {
this.otoId = otoId;
return this;
}
public Builder customerWishDate(LocalDate customerWishDate) {
this.customerWishDate = customerWishDate;
return this;
}
}
}
OrderNr.java:
#NoArgsConstructor(access = PROTECTED)
#EqualsAndHashCode
#Getter
#ToString
#Embeddable
public class OrderNr implements Serializable {
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "ORDER_NR")
private long value;
public OrderNr(Long value) {
checkArgument(nonNull(value), "OrderNr value must not be null");
this.value = value;
}
}

How to send only the ID the of main nested objects in the body request in spring boot

I'm creating eCommerce for merchants using spring boot with JPA.
I have an issue while creating the order service.
I want to only pass the ID of the nested objects in the request body instead of sending the full nest objects because the size will be extremely big.
Here is my code.
Merchant can do many orders
Order
#Entity
#Table(name = "Orders")
#XmlRootElement
#JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
public class Order extends BasicModelWithIDInt {
#Basic(optional = false)
#Column(name = "Quantity")
private Integer quantity;
#Basic(optional = false)
#Size(min = 1, max = 150)
#Column(name = "Notes")
private String notes;
#JoinColumn(name = "ProductID", referencedColumnName = "ID")
#ManyToOne(optional = false, fetch = FetchType.LAZY)
#JsonIgnoreProperties
private Product productID;
#JoinColumn(name = "MerchantID", referencedColumnName = "ID")
#ManyToOne(optional = false, fetch = FetchType.LAZY)
private Merchent merchent;
#JoinColumn(name = "OrderSatusID", referencedColumnName = "ID")
#ManyToOne(optional = false, fetch = FetchType.EAGER)
private OrderStatus orderStatus;
// Getters and Setters
}
Order Holder
public class OrderHolder {
#NotNull
private Order order;
public Order getOrder() {
return order;
}
public void setOrder(Order order) {
this.order = order;
}
}
OrderRepo
public interface OrderRepo extends JpaRepository<Order, Integer> {
}
Order Controller
#RestController
#RequestMapping(value = "order", produces = MediaType.APPLICATION_JSON_VALUE)
public class OrderRestController extends BasicController<OrderHolder>{
#Autowired
private OrderRepo orderRepo;
#PostMapping("create")
public ResponseEntity<?> create(#RequestBody #Valid OrderHolder orderHolder, Principal principal) throws GeneralException {
log.debug( "create order {} requested", orderHolder.toString());
Order order = new Order();
order = orderHolder.getOrder();
System.out.println("###############"+order);
try {
order = orderRepo.save(order);
log.info( "Order {} has been created", order );
} catch (Exception e) {
log.error( "Error creating Order: ", e );
e.printStackTrace();
throw new GeneralException( Errors.ORDER_CREATION_FAILURE, e.toString() );
}
return ResponseEntity.ok( order );
}
}
I need request body to look like the below instead of including the full Merchant and Product objects inside the request.
You can make use of JsonView to return only id of product and merchant
public class OrderView {}
...
public class Product{
#Id
#JsonView(OrderView.class)
private Integer id
private String otherFieldWithoutJsonView
...
}
and then in your controller
#PostMapping("create")
#JsonView(OrderView.class) // this will return the product object with one field (id)
public ResponseEntity<?> create(#RequestBody #Valid OrderHolder orderHolder, Principal principal) throws GeneralException {
...
}
hope this can help you
Just have a separate contract class.
public class OrderContract {
private int merchantID;
private String notes;
....
//getter, setters
}
public class OrderHolder {
#NotNull
private OrderContract orderContract;
public OrderContract getOrderContract() {
return orderContract;
}
public void setOrder(OrderContract orderContract) {
this.orderContract = orderContract;
}
}
And before making a call to the Repository , translate from OrderContract to Order.
I would like to share something regarding this.
I have searched a lot on internet and tried lot of things, but the solution given here suited well for this scenario.
https://www.baeldung.com/jackson-deserialization
You need to create a Custom-deserializer for your model by extending StdDeserializer from com.fasterxml.jackson.databind.deser.std.StdDeserializer, where you just want to pass id's and not the whole object in the request.
I have given below example for User Model with Address object.
User(long userId, String name, Address addressId)
Address(long addressId, String wholeAddress)
Writing Deserializer for User class
public class UserDeserializer extends StdDeserializer<User> {
public User() {
this(null);
}
public User Deserializer(Class<?> vc) {
super(vc);
}
#Override
public User deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JacksonException {
JsonNode node = p.getCodec().readTree(p);
long id = 0;
long addressId = (Long) ((IntNode) node.get("addressId")).numberValue().longValue();
return new User(id, name, new Address(addressId, null)
}
Now you have to use
#JsonDeserialize(using = UserDeserializer.class)
public Class User {
...
}
POST request
Before custom deserialization
{
"name" : "Ravi",
"addressId" : { "id" : 1}
}
After custom Deserialization
{
"name" : "Ravi",
"addressId" : 1
}
Also while GET /user/:id call you will get the whole obj like
{
"name" : "Ravi",
"addressId" : { "id" : 1, "wholeAddress" : "Some address"}
}

CrudRepository filter for String value in List<String> property

basically I got following entity (extended by Lombok)
#Getter
#Setter
#Entity("FOO")
public class Foo{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ID", unique = true, nullable = false)
private long id;
#ManyToOne
#JoinColumn(name = "FK_FEE", nullable = false)
private Fee fee;
#Column(name = "CCCS")
#Convert(converter = StringListConverter.class)
private List<String> cccs;
}
And the StringListConverter:
#Converter
public class StringListConverter implements AttributeConverter<List<String>, String> {
#Override
public String convertToDatabaseColumn(final List<String> list) {
String returnValue = null;
if (list != null) {
final List<String> trimmedList = new ArrayList<>();
for (final String strg : list) {
if (strg != null && !strg.isEmpty()) {
trimmedList.add(strg.trim());
}
}
returnValue = String.join(",", trimmedList);
}
return returnValue;
}
#Override
public List<String> convertToEntityAttribute(final String joined) {
List<String> returnValue = null;
if (joined != null) {
returnValue = new ArrayList<>();
final String[] splitted = joined.split(",");
for (final String strg : splitted) {
if (strg != null && !strg.isEmpty()) {
returnValue.add(strg.trim());
}
}
}
return returnValue;
}
}
And now I want to fetch a List of Foo where Fee.Id= 123 and Foo.cccs contains a specific string value.
#Repository
public interface FooRepository extends CrudRepository<Foo, Long> {
List<Foo> findByFeeIdAndCccsIn(Long feeId, String ccc);
}
But this does not work. Is the only way to solve this by writing an own query?
#Repository
public interface FooRepository extends CrudRepository<Foo, Long> {
#Query( "Select foo FROM Foo foo WHERE foo.fee.id = :feeId AND foo.cccs LIKE CONCAT(CONCAT('%', :cccs) ,'%')")
List<Foo> findByFeeIdAndCccsIn(#Param("feeId") Long feeId,#Param("cccs") String ccc);
}
Currently it seems the only solution is to use #Query and write a JPQL or native SQL query to get the data.
String SELECT_BY_FEE_AND_CCC = "Select foo FROM Foo foo "
+ " WHERE foo.fee.id = :feeId AND foo.cccs LIKE CONCAT(CONCAT('%', :ccc) ,'%')";
and attached with the #Query annotation like:
#Query(SELECT_BY_FEE_AND_CCC)
List<Foo> findByFeeIdAndCccsContains(#Param("feeId") Long feeId, #Param("ccc") String ccc);
NOTE/EDIT:
Sometimes you have to join the foreign table to have access on it's properties.
String SELECT_BY_FEE_AND_CCC = "Select foo FROM Foo foo "
+ " JOIN foo.fee fee "
+ " WHERE fee.id = :feeId AND foo.cccs LIKE CONCAT(CONCAT('%', :ccc) ,'%')";
And now I want to fetch a List of Foo where Fee.Id= 123 and Foo.cccs
contains a specific string value.
You have specific requirements. According to Spring Data JPA doc what you are trying to do is not supported.
So you should use #Query for your requirements.

Could not write JSON: Could not set field value [t] by reflection

I'm working on a Spring Boot 2.0.5.RELEASE project.
I have a field in an Oracle database declared as CHAR(1) with a JPA converter as follows:
public class CharToBooleanConverter implements AttributeConverter<String, Boolean> {
#Override
public Boolean convertToDatabaseColumn(String s) {
return s.equalsIgnoreCase("t");
}
#Override
public String convertToEntityAttribute(Boolean aBoolean) {
if(aBoolean.equals(true)){
return "t";
} else {
return "f";
}
}
}
This converter is used in my StructureElement class twice:
#Entity
#Table(name = "OBS_STRUCTURE_ELEMENT2")
#SequenceGenerator(name = "structure_element_seq", sequenceName = "structure_element_seq", allocationSize = 1)
public class StructureElement {
#Id
#Column(name = "NO_ELEMENT")
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "structure_element_seq")
private long id;
#Column(name = "TAG")
private String tag;
#Column(name = "DESCRIPTION")
private String description;
#Column(name = "SUITE")
private int sequence;
#Column(name = "OPTIONNEL")
#Convert(converter = CharToBooleanConverter.class)
private boolean optional;
#Column(name = "REPETITIF")
#Convert(converter = CharToBooleanConverter.class)
private boolean repetitive;
#ManyToOne
#JoinColumn(name = "NOM_STRUCTURE_TYPE")
private Structure typeStructure;
#Embedded
private PersistenceSignature signature;
}
The problem is that when I try to send a Structure through a RestController I receive the following in console:
Resolved [org.springframework.http.converter.HttpMessageNotWritableException: Could not write JSON: Could not set field value [t] value by reflection : [class be.solodoukhin.domain.StructureElement.optional] setter of be.solodoukhin.domain.StructureElement.optional; nested exception is com.fasterxml.jackson.databind.JsonMappingException: Could not set field value [t] value by reflection : [class be.solodoukhin.domain.StructureElement.optional] setter of be.solodoukhin.domain.StructureElement.optional (through reference chain: be.solodoukhin.domain.Structure["elements"])]
With an 500 Internal Server Error response.
Here is my method:
#RestController
#RequestMapping("/structure")
public class StructuresController {
#GetMapping("/{name}")
public ResponseEntity<Structure> getOne(#PathVariable("name") String name)
{
LOGGER.info("Call to StructuresController.getOne with name = " + name);
Optional<Structure> found = this.structureRepository.findById(name);
if(found.isPresent()){
return ResponseEntity.ok(found.get());
}
else
{
return ResponseEntity.badRequest().body(null);
}
}
}
I've tried to use hibernate specific annotation #Type(type = "true_false"). It works but this produces an uppercase T or F in the database.
I've tried to write a JSON serializer that extends com.fasterxml.jackson.databind.ser.std.StdSerializer using this link without success.
Isn't your converter the wrong way round? Your convertToDatabaseColumn has it going from a String to a Boolean. Surely you want it going from Boolean to String. And the convertToEntityAttribute going from String to Boolean.
I suspect that the convert is returning a value of "t" or "f" which can't then be put into the boolean field in the entity.
I think it should be...
public class CharToBooleanConverter implements AttributeConverter<Boolean, String> {
#Override
public Boolean convertToEntityAttribute(String s) {
return s != null && s.equalsIgnoreCase("t");
}
#Override
public String convertToDatabaseColumn(Boolean aBoolean) {
return (aBoolean != null && aBoolean) ? "t" : "f";
}
}

Categories