I'm writing a class instance to a MySQL DB using MyBatis
// I have no control over how this java class is laid out -.-
class Hello {
boolean isFriendly
}
my MyBatis Mapper looks like this
<insert id="doHello" parameterType="Hello">
insert into hello_table (
is_friendly --this is a varchar(1) btw
)
values (
#{isFriendly}
)
</insert>
The problem is it inserts the values into the DB as 0 or 1, but I need to have it as 'N' or 'Y'
and I don't have the choice of modifying the java
I'm trying to keep my code as minimal as possible and ideally would like to add stuff into the Mybatis Mapper
I tried things like
#{isFriendly,jdbcType=Boolean}
but it didn't work
MyBatis typeHandler is a proper way to do it.
You could implement a type handler and then use it in any sql statement:
#{isFriendly, typeHandler=YesNoBooleanTypeHandler}
For the details see MyBatis Java Boolean to Sql enum
Modify your insert statement
insert into hello_table (
is_friendly
)values (${isFriendly=="0"?"'N'":"'Y'"})
create handler and add it in Mybatis configuration
create handler:
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.MappedJdbcTypes;
import org.apache.ibatis.type.MappedTypes;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
#MappedTypes(Boolean.class)
#MappedJdbcTypes(JdbcType.CHAR)
public class BooleanStringTypeHandler extends BaseTypeHandler {
#Override
public void setNonNullParameter(PreparedStatement preparedStatement, int i, Boolean aBoolean, JdbcType jdbcType) throws SQLException {
preparedStatement.setString(i, aBoolean ? "Y" : "N");
}
#Override
public Boolean getNullableResult(ResultSet resultSet, String s) throws SQLException {
return getBoolean(resultSet.getString(s));
}
#Override
public Boolean getNullableResult(ResultSet resultSet, int i) throws SQLException {
return getBoolean(resultSet.getString(i));
}
#Override
public Boolean getNullableResult(CallableStatement callableStatement, int i) throws SQLException {
return getBoolean(callableStatement.getString(i));
}
private Boolean getBoolean(String s) {
return "Y".equalsIgnoreCase(s);
}
}
add it in your Mybatis configuration:
<typeHandlers>
<typeHandler jdbcType="CHAR" javaType="java.lang.Boolean" handler="crm.data.trade.utils.BooleanStringTypeHandler"/>
</typeHandlers>
Related
I'm new to spring r2dbc. Previously I've user hibernate. In hibernate when you need to map postgresql enum to java enum you just add com.vladmihalcea:hibernate-types-52 and use #Enumerated (as shown bellow). Related to R2DBC and enum (PostgreSQL) SO question I have to create codec for every single enum. Is it possible to achive this with some kind of tag or other general solution not just creating multiple codecs.
CREATE TYPE user_type_enum AS ENUM ('ADMIN', 'USER');
public class PostgreSQLEnumType extends org.hibernate.type.EnumType {
public void nullSafeSet(PreparedStatement st, Object value,
int index, SharedSessionContractImplementor session)
throws HibernateException, SQLException {
st.setObject(
index,
value != null ? ((Enum) value).name() : null,
Types.OTHER
);
}
}
public enum UserTypeEnum {
ADMIN,
USER
}
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import org.springframework.data.annotation.Id
import org.springframework.data.relational.core.mapping.Table;
import org.hibernate.annotations.Type;
import org.hibernate.annotations.TypeDef;
#Table;
#TypeDef(
name = "pgsql_enum",
typeClass = PostgreSQLEnumType.class
)
public class User {
#Id
private Long id;
private String usename;
#Enumerated(EnumType.STRING)
#Type(type = "pgsql_enum")
private UserEnumType userType;
// Getters and setters provided
}
You don't need to create your own codecs anymore.
See https://github.com/pgjdbc/r2dbc-postgresql#postgres-enum-types
DDL:
CREATE TYPE my_enum AS ENUM ('FIRST', 'SECOND');
Java Enum class:
enum MyEnumType {
FIRST, SECOND;
}
Your R2DBC ConnectionFactory bean:
PostgresqlConnectionConfiguration.builder()
.codecRegistrar(EnumCodec.builder()
.withEnum("my_enum",MyEnumType.class)
.build());
Note that you must use lower case letter for your "my_enum" in withEnum, otherwise won't work.
Also, you will need to provide a converter that extends EnumWriteSupport, and register it.
See: https://docs.spring.io/spring-data/r2dbc/docs/current/reference/html/#mapping.explicit.enum.converters
For example:
#Configuration
public static class R2DBCConfiguration extends AbstractR2dbcConfiguration {
#Override
#Bean
public ConnectionFactory connectionFactory() {
...
}
#Override
protected List<Object> getCustomConverters() {
return List.of(
new MyEnumTypeConverter()
);
}
}
Trying to solve Postgresql Array Functions with QueryDSL more cleanly, I've got this far.
// obj.foo is an ArrayPath<String[], String>
bindings.bind(obj.foo).first((path, value) ->
Expressions.booleanTemplate("arraycontains({0}, {1}) = true", path, value));
this ends up as correct-looking SQL
where arraycontains(obj0_1_.foo, ?)=true
but it seems the String[] variable is not passed correctly
org.postgresql.util.PSQLException: ERROR: function arraycontains(character varying[], bytea) does not exist
How can I either (if possible)
get the String[] value to bind as a varchar[]?
express the necessary cast in the booleanTemplate?
Instead of passing the String[] directly, wrap it in a TypedParameterValue.
The hibernate-types library does not yet support varchar[], but you can use it to build something that does:
public class VarcharArrayType extends AbstractHibernateType<String[]> {
public static VarcharArrayType INSTANCE = new VarcharArrayType();
public VarcharArrayType() {
super(ArraySqlTypeDescriptor.INSTANCE, new TypeDescriptor());
}
public String getName() {
return "varchar-array";
}
public static class TypeDescriptor extends StringArrayTypeDescriptor {
#Override
protected String getSqlArrayType() {
return "varchar";
}
}
}
I'm new to mybatis. I am trying to map a JDBC integer to a custom class. All the examples that I have seen on this have used annotations, is it possible to not use annotations and do this? Any example would be greatly appreciated.
Sreekanth
It is definitely possible and is described in general in Configuration and in Mapper sections of the documentation.
Define the handler first:
#MappedJdbcTypes(JdbcType.INTEGER)
public class MyClassHandler extends BaseTypeHandler<MyClass> {
#Override
public void setNonNullParameter(PreparedStatement ps, int i,
MyClass parameter, JdbcType jdbcType) throws SQLException {
ps.setInt(i, parameter.asInt());
}
#Override
public MyClass getNullableResult(ResultSet rs, String columnName)
throws SQLException {
int val = rs.getInt(columnName);
if (rs.wasNull())
return null;
else
return MyClass.valueOf(val);
}
#Override
public MyClass getNullableResult(ResultSet rs, int columnIndex)
throws SQLException {
int val = rs.getInt(columnIndex);
if (rs.wasNull())
return null;
else
return MyClass.valueOf(val);
}
#Override
public MyClass getNullableResult(CallableStatement cs, int columnIndex)
throws SQLException {
int val = cs.getInt(columnIndex);
if (cs.wasNull())
return null;
else
return MyClass.valueOf(val);
}
}
Then configure it in mybatis-config.xml:
<typeHandlers>
<typeHandler handler="my.company.app.MyClassHandler"/>
</typeHandlers>
Now you can use it in xml mappers.
If you have a class
class SomeTypeEntity {
private MyClass myClassField;
};
For querying the field configure handler in the resultMap like this:
<resultMap id="someMap" type="SomeTypeEntity">
<result property="myClassField" column="my_class_column" typeHandler="my.company.app.MyClassHandler"/>
</resultMap>
For insert/update use it like this:
<update id="updateSomeTypeWithMyClassField">
update some_type
set
my_class_column = #{someTypeEntity.myClassField, typeHandler=my.company.app.MyClassHandler},
</update>
for mapper method:
void updateSomeTypeWithMyClassField(#Param("someTypeEntity") SomeTypeEntity entity);
i'm trying to run an imported open source but i'm getting this error after running :
The method nullSafeSet(PreparedStatement, Object, int, SessionImplementor) of type
BlobUserType must override or implement a supertype method
here's the method i diidn't put anything on it because i don't really need it , but i have to averride it .
#Override
protected void nullSafeSet(PreparedStatement ps, Object value , int index ,
SessionImplementor si) throws SQLException{}
Here's the class code :
package org.squashtest.csp.tm.internal.infrastructure.hibernate;
import java.io.IOException;
import java.io.InputStream;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import org.hibernate.HibernateException;
import org.springframework.jdbc.support.lob.LobCreator;
import org.springframework.jdbc.support.lob.LobHandler;
import org.springframework.orm.hibernate3.support.AbstractLobType;
import org.hibernate.engine.SessionImplementor;
public class BlobUserType extends AbstractLobType {
#Override
public int[] sqlTypes() {
return new int[] {Types.BLOB};
}
#Override
public Class<?> returnedClass() {
return InputStream.class;
}
#Override
protected Object nullSafeGetInternal(ResultSet rs, String[] names,
Object owner, LobHandler lobHandler) throws SQLException,
IOException, HibernateException {
return lobHandler.getBlobAsBinaryStream(rs, names[0]);
}
#Override
protected void nullSafeSetInternal(PreparedStatement ps, int index, Object
value, LobCreator lobCreator) throws SQLException,
IOException, HibernateException {
if (value != null) {
lobCreator.setBlobAsBinaryStream(ps, index, (InputStream) value,
-1);
}
else {
lobCreator.setBlobAsBytes(ps, index, null);
}
}
}
This method is written against different version of Hibernate than what you are using. As you see, UserType in Hibernate 3.5 for example does have following:
void nullSafeSet(PreparedStatement st,
Object value,
int index) throws HibernateException, SQLException
Hibernate 4.1 on the other hand does have such a UserType class where method signature matches to your method:
void nullSafeSet(PreparedStatement st,
Object value,
int index,
SessionImplementor session)
throws HibernateException, SQLException
I am currently working on a report which needs a group_concat for one of the fields.
CriteriaQuery<GameDetailsDto> criteriaQuery = criteriaBuilder
.createQuery(GameDetailsDto.class);
Root<BetDetails> betDetails = criteriaQuery.from(BetDetails.class);
Expression<String> betSelection = betDetails.get("winningOutcome");
criteriaQuery.multiselect(
// other fields to select
criteriaBuilder.function("group_concat", String.class, betSelection),
// other fields to select
);
//predicate, where clause and other filters
TypedQuery<GameDetailsDto> typedQuery = entityManager.createQuery(criteriaQuery);
this throws a null pointer exception on the line:
TypedQuery<GameDetailsDto> typedQuery = entityManager.createQuery(criteriaQuery);
did i incorrectly use the function method of the criteriaBuilder?
the documentations says:
function(String name, Class<T> type, Expression<?>... args);
I figured out how to do this with Hibernate-jpa-mysql:
1.) created a GroupConcatFunction class extending org.hibernate.dialect.function.SQLFunction (this is for single column group_concat for now)
public class GroupConcatFunction implements SQLFunction {
#Override
public boolean hasArguments() {
return true;
}
#Override
public boolean hasParenthesesIfNoArguments() {
return true;
}
#Override
public Type getReturnType(Type firstArgumentType, Mapping mapping)
throws QueryException {
return StandardBasicTypes.STRING;
}
#Override
public String render(Type firstArgumentType, List arguments,
SessionFactoryImplementor factory) throws QueryException {
if (arguments.size() != 1) {
throw new QueryException(new IllegalArgumentException(
"group_concat shoudl have one arg"));
}
return "group_concat(" + arguments.get(0) + ")";
}
}
2.) i created the CustomMySql5Dialect class extending org.hibernate.dialect.MySQL5Dialect and registered the group_concat class created in step 1
3.) On the app context, i updated the jpaVendorAdapter to use the CustomMySql5Dialect as the databasePlatform
4.) Finally to use it
criteriaBuilder.function("group_concat", String.class,
sampleRoot.get("sampleColumnName"))
Simple solution: instead of creating the whole class, just use SQLFunctionTemplate.
new SQLFunctionTemplate(StandardBasicTypes.STRING, "group_concat(?1)")
and then register this function in your own SQL dialect (eg. in constructor)
public class MyOwnSQLDialect extends MySQL5Dialect {
public MyOwnSQLDialect() {
super();
this.registerFunction("group_concat", new SQLFunctionTemplate(StandardBasicTypes.STRING, "group_concat(?1)"));
}
}
Suggested property:
spring.jpa.properties.hibernate.metadata_builder_contributor = com.inn.core.generic.utils.SqlFunctionsMetadataBuilderContributor
and class:
import org.hibernate.boot.MetadataBuilder;
import org.hibernate.boot.spi.MetadataBuilderContributor;
import org.hibernate.dialect.function.StandardSQLFunction;
import org.hibernate.type.StandardBasicTypes;
import org.springframework.stereotype.Component;
#Component
public class SqlFunctionsMetadataBuilderContributor implements MetadataBuilderContributor {
#Override
public void contribute(MetadataBuilder metadataBuilder) {
metadataBuilder.applySqlFunction("config_json_extract",
new StandardSQLFunction("json_extract", StandardBasicTypes.STRING));
metadataBuilder.applySqlFunction("JSON_UNQUOTE",
new StandardSQLFunction("JSON_UNQUOTE", StandardBasicTypes.STRING));
metadataBuilder.applySqlFunction("group_concat",
new StandardSQLFunction("group_concat", StandardBasicTypes.STRING));
}
}