Spring Jpa Projection interface occur error with Boolean type - mysql

Q. Why JPA Projection can't convert Mysql bit(1) to Java Boolean?
Spring Jpa Projection occur error Projection type must be an interface! when the Mysql bit(1) type maps to the Java Boolean type.
Jpa converts a Boolean column in Entity class to bit(1) column in Mysql Table.
If I change getIsBasic's type in PlanInfoProjection interface Integer to Boolean, It doesn't work. Why does it occur error?
JPA Repository
#Query(nativeQuery=true, value="select true as isBasic from dual")
ProductOrderDto.PlanInfoProjection findPlanInfoById(Long id);
Projection interface
public class ProductOrderDto {
#Getter
public static class PlanInfo {
private Boolean isBasic;
public PlanInfo(PlanInfoProjection projection) {
// this.isBasic = projection.getIsBasic(); //<-- I want to use like this.
if (projection.getIsBasic() == null) {
this.isBasic = null;
} else {
this.isBasic = projection.getIsBasic() == 0 ? false : true; // <-- I have to convert
}
}
}
public interface PlanInfoProjection {
Integer getIsBasic(); // It works, but I have to convert Integer to Boolean to use.
//Boolean getIsBasic(); // doesn't work, but why???
//Boolean isBasic(); // also doesn't work
//boolean isBasic(); // also doesn't work
}
}

It seems like this doesn't work out of the box. What works for me (although I'm using DB2 so my datatype is different but this shouldn't be a problem) is to annotate it and use SpEL like this:
#Value("#{target.isBasic == 1}")
boolean getIsBasic();
This just takes your int value (0 for false, 1 for true) and creturns a boolean value. Should also work with Boolean but I didn't test it.
Another option is to use #Value("#{T(Boolean).valueOf(target.isBasic)}") but this only works for String values, so you would have to store 'true' or 'false' in your database. With T() you can import Static classes into Spring Expression Language, and then just call the valueOf method which returns a boolean (either Boolean or boolean)

Related

Implicitly convert old version of a data model

I have a data structure and I am serializing this data structure to a JSON-File using Jackson Databind. As time progresses, the data model changes, but my application still needs to be able to read an old version of the JSON. My intention is that when an old version of the JSON is encountered, it is implicitly converted to the new version of the data structure in memory and when serialized for the next time, it is stored as the new format version.
For the case of newly added properties, this is simple: I simply specify a default value in Kotlin and Jackson uses that default value if the property is missing in the JSON. However, this case is more complicated: Previously, I had the following data structure:
data class Options(
var applyClahePerColorChannel: Boolean = false
)
Now, I want to make this more general and change the data structure to the following:
data class Options(
var multichannelMode: MultichannelMode = MultichannelMode.ApplyToLuminance
)
enum class MultichannelMode {
ApplyToLuminance, ApplyToAllColorsSeparately
}
Now, when reading an old version of the JSON, applyClahePerColorChannel == false should implicitly be translated to multichannelMode == ApplyToLuminance and applyClahePerColorChannel == true to multichannelMode == ApplyToAllColorsSeparately.
How can I achieve that in Jackson in a concise way?
Here's a solution I found, but I'm still open to better suggestions.
A simple renamed property can be handled with #JsonAlias like so:
If this data model...
data class Options(
var applyClahePerColorChannel: Boolean = false
)
...changes to...
data class Options(
var applyPerColorChannel: Boolean = false
)
...the old name can be added as an alias like so:
data class Options(
#get:JsonAlias("applyClahePerColorChannel")
var applyPerColorChannel: Boolean = false
)
Jackson will then treat both the same way, but upon serialization, Jackson will use the new name.
However, in my case, I also changed the type of the variable, requiring a custom converter like so:
data class Options(
#get:JsonAlias("applyClahePerColorChannel")
#get:JsonDeserialize(converter = BooleanToMultichannelModeConverter::class)
var multichannelMode: MultichannelMode = MultichannelMode.ApplyToLuminance
)
class BooleanToMultichannelModeConverter : Converter<String, MultichannelMode> {
override fun convert(value: String): MultichannelMode {
return when (value) {
in listOf("true", "True", "TRUE") -> ApplyToAllColorsSeparately
in listOf("false", "False", "FALSE") -> ApplyToLuminance
else -> MultichannelMode.valueOf(value)
}
}
#OptIn(ExperimentalStdlibApi::class)
override fun getInputType(typeFactory: TypeFactory): JavaType = typeFactory
.constructType(String::class.starProjectedType.javaType)
#OptIn(ExperimentalStdlibApi::class)
override fun getOutputType(typeFactory: TypeFactory): JavaType = typeFactory
.constructType(MultichannelMode::class.starProjectedType.javaType)
}
The converter basically tells Jackson to not do any parsing on its own and instead simply hand the unparsed string to the converter class, which will first attempt to parse the string as a boolean and if that fails as an enum value.

EF Core Full-text search: parameterized keySelector could not be translated into SQL

I'd like to create a generic extension method which allow me to use Full-text search.
● The code below works:
IQueryable<MyEntity> query = Repository.AsQueryable();
if (!string.IsNullOrEmpty(searchCondition.Name))
query = query.Where(e => EF.Functions.Contains(e.Name, searchCondition.Name));
return query.ToList();
● But I want a more-generic-way so I create the following extension method
public static IQueryable<T> FullTextContains<T>(this IQueryable<T> query, Func<T, string> keySelector, string value)
{
return query.Where(e => EF.Functions.Contains(keySelector(e), value));
}
When I call the exxtension method like below, I got an exception
IQueryable<MyEntity> query = Repository.AsQueryable();
if (!string.IsNullOrEmpty(searchCondition.Name))
query = query.FullTextContains(e => e.Name, searchCondition.Name);
return query.ToList();
> System.InvalidOperationException: 'The LINQ expression 'DbSet
> .Where(c => __Functions_0
> .Contains(
> _: Invoke(__keySelector_1, c[MyEntity])
> ,
> propertyReference: __value_2))' could not be translated. Either rewrite the query in a form that
> can be translated, or switch to client evaluation explicitly by inserting a call to either AsEnumerable(),
> AsAsyncEnumerable(), ToList(), or ToListAsync().
> See https://go.microsoft.com/fwlink/?linkid=2101038 for more information
>
How do I "rewrite the query in a form that can be translated" as the Exception suggested?
Your are falling into the typical IQueryable<> trap by using delegates (Func<>) instead of expressions (Expression<Func<>). You can see that difference in the lambda arguments of every Queryable extension method vs corresponding Enumerable method. The difference is that the delegates cannot be translated (they are like unknown methods), while the expressions can.
So in order to do what you want, you have to change the signature of the custom method to use expression(s):
public static IQueryable<T> FullTextContains<T>(
this IQueryable<T> query,
Expression<Func<T, string>> keySelector, // <--
string value)
But now you have implementation problem because C# does not support syntax for "invoking" expressions similar to delegates, so the following
keySelector(e)
does not compile.
In order to do that you need at minimum a small utility for composing expressions like this:
public static partial class ExpressionUtils
{
public static Expression<Func<TOuter, TResult>> Apply<TOuter, TInner, TResult>(this Expression<Func<TOuter, TInner>> outer, Expression<Func<TInner, TResult>> inner)
=> Expression.Lambda<Func<TOuter, TResult>>(inner.Body.ReplaceParameter(inner.Parameters[0], outer.Body), outer.Parameters);
public static Expression<Func<TOuter, TResult>> ApplyTo<TInner, TResult, TOuter>(this Expression<Func<TInner, TResult>> inner, Expression<Func<TOuter, TInner>> outer)
=> outer.Apply(inner);
public static Expression ReplaceParameter(this Expression expression, ParameterExpression source, Expression target)
=> new ParameterReplacer { source = source, target = target }.Visit(expression);
class ParameterReplacer : ExpressionVisitor
{
public ParameterExpression source;
public Expression target;
protected override Expression VisitParameter(ParameterExpression node)
=> node == source ? target : node;
}
}
Use Apply or ApplyTo depending of what type of expression you have. Other than that they do the same.
In your case, the implementation of the method with Expression<Func<T, string>> keySelector would be
return query.Where(keySelector.Apply(key => EF.Functions.Contains(key, value)));

Lower casing column values dynamically

I have a mysql script and I want to convert a column value to be always in lower case. I don't want to use trigger. When I run my hibernate code and fill data in DB I want a column value to be always in lowercase.
Is there is any way I can use Lower() function of mysql during table creation so that every time data is inserted it is lower Case?
I saw many examples of lowercase but all are update operation.
You can use Hibernate interceptor (see an example here)
In the method
public boolean onSave(Object entity,Serializable id,
Object[] state,String[] propertyNames,Type[] types)
You can check whether the entity is instance of your class to be lowercased and update necessary fields with lower cased values.
Or just in the entity extend setters to convert to the lowercase on call.
Assume you have an entity like:
#Entity
#EntityListeners(MyEntityListener.class)
public class MyEntity {
private String name;
...
}
and EntityListener like:
import javax.persistence.PrePersist;
public class MyEntityListener {
#PrePersist
public void entityPrePersist(MyEntity obj) {
if (obj != null && obj.getName() != null) {
obj.setName(obj.getName().toLowerCase());
}
// ... same to other properties
}
}

JPA Unary Operators

I want to know if it is possible to set a boolean value in JPA with Unary Operator.
I mean something like this
#Modifying
#Query("update Computer com set com.enabled=!com.enabled where com.id = ?1")
The enabled field is mapped like this in the POJO
private Boolean enabled;
public Boolean getEnabled() {
return enabled;
}
public void setEnabled(Boolean enabled) {
this.enabled = enabled;
}
in the DB it is stored as boolean(1)
this is the result
Caused by: java.lang.IllegalArgumentException: org.hibernate.QueryException: expecting '=', found 'c' [update com.nicinc.Computer com set com.enabled=!com.enabled where com.id = ?1]
and here the JPA properties
Spring Data JPA properties
spring.datasource.url=jdbc:h2:mem:testdb;MODE=MySQL;DB_CLOSE_ON_EXIT=FALSE
spring.datasource.username=sa
spring.datasource.password=
spring.jpa.show-sql=false
spring.jpa.properties.hibernate.format_sql=true
hibernate.dialect=org.hibernate.dialect.H2Dialect
Different databases have different support for handling of boolean expressions, so there is little room for JPA to provide a generalized approach, thus you have to be explicit:
update Computer
set enabled = case when enabled = true then false else true end
where id = ?1
Probably you are using tinyint on the MySQL side for that column and persistence provider cannot distinguish 0 and 1 as being false or true.
If you are using JPA 2.1 then i would suggest creating a global converter for boolean conversion:
#Converter(autoApply=true)
public class GlobalBooleanConverter implements AttributeConverter<Boolean, Integer>{
#Override
public String convertToDatabaseColumn(Boolean value) {
if (Boolean.TRUE.equals(value)) {
return Integer.valueOf(1);
} else {
return Integer.valueOf(0);
}
}
#Override
public Boolean convertToEntityAttribute(String value) {
return Integer.valueOf(1).equals(value);
}
}
A bit out of the box alternative would be to change the POJO field to Integer and change the query to following:
#Modifying
#Query("update Computer com set com.enabled=((-com.enabled)+1) where com.id = ?1")

Mvc binding issue from json to enum (customexception from int to enum)

i have this problem: i use the json to send data to server.
All works fine but the problem is a situation like:
public enum SexType
{
Male : 0,
Female : 1
}
class People{
public SexType Sex {get;set;}
}
That create me the json:
{"Sex" : 0}
When i send back to server, this fill the ModelStateError with this issue:
The parameter conversion from type 'System.Int32' to type 'SexType' failed because no type converter can convert between these types.
But if i wrap the value with ' all work well:
{"Sex" : '0'}
Anyone have the same problem?
Tnx for all!
Yes, I got the same problem. The weird problem is that if you sent back:
{"Sex" : 'Male'}
it would deserialize no problem.
To solve the problem, I implemented a custom model binder for enums, leveraging the example found here (slightly modified as there were some errors):
http://eliasbland.wordpress.com/2009/08/08/enumeration-model-binder-for-asp-net-mvc/
namespace yournamespace
{
/// <summary>
/// Generic Custom Model Binder used to properly interpret int representation of enum types from JSON deserialization, including default values
/// </summary>
/// <typeparam name="T">The enum type to apply this Custom Model Binder to</typeparam>
public class EnumBinder<T> : IModelBinder
{
private T DefaultValue { get; set; }
public EnumBinder(T defaultValue)
{
DefaultValue = defaultValue;
}
#region IModelBinder Members
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
return bindingContext.ValueProvider.GetValue(bindingContext.ModelName) == null ? DefaultValue : GetEnumValue(DefaultValue, bindingContext.ValueProvider.GetValue(bindingContext.ModelName).AttemptedValue);
}
#endregion
public static T GetEnumValue<T>(T defaultValue, string value)
{
T enumType = defaultValue;
if ((!String.IsNullOrEmpty(value)) && (Contains(typeof(T), value)))
enumType = (T)Enum.Parse(typeof(T), value, true);
return enumType;
}
public static bool Contains(Type enumType, string value)
{
return Enum.GetNames(enumType).Contains(value, StringComparer.OrdinalIgnoreCase);
}
}
}
and then registering the model binder in global.asax.cs.
In your case it would be something like:
ModelBinders.Binders.Add(typeof(SexType), new EnumBinder<SexType>(SexType.Male));
I am not sure if there is a quicker way, but this works great.
The Model binding uses the Enum.Parse() method, which is fairly smart about interpreting strings but does NOT explicitly cast or convert other types into strings, even if system-level facilities exist to do so and even if they're the internal storage type used within the Enum.
Is this the right behavior? Arguably so, since if you don't know enough to convert your Enum values to strings you might not be aware that the right-hand side of the Enum values are not necessarily unique within the Enum, either.
As a matter of personal taste (and this is probably also because I do way too much statistical analysis programming) for sex I generally prefer to define it as a clear boolean value, i.e. instead of differentiating between arbitrary values for 'Male' and 'Female' I use a variable called e.g. IsFemale and set it to true or false. This plays more nicely with json, since it relies on primitive types common to both languages, and requires less typing when you want to use it.