I feel like this is a simple problem, but none of the things i tried work for me. I have an enum, the reason i have string constructor is because Java doesn't allow enum to be numerical..I tried AA, AB, 2C directly without string constructor but that gives an error. Note that for the existing enum i am adding C("2C").
public enum TestEnum{
AA("AA"), AB("AB"), C("2C");
private String display;
private TestEnum( String display ) {
this.display = display;
}
public String toString() {
return display;
}
public String getDisplay() {
return display;
}
public void setDisplay( String display ) {
this.display = display;
}
public String getName() {
return display;
}
Now i have a mybatis mapper which does a merge this is existing and one of the param to the mapper is TestEnum. Until now this worked fine since enum value and string value are same, but i added C("2C"). Now i want to insert 2C to the table using mybaits, but it always inserts C.
merge into text t
using (select #{id} as id from dual) d on (d.id = t.id)
when matched then
update set
appId = #{applId},
src = #{testEnum}
testEnum inserts C, so i changed that to #{testEnum.toString()} which gave me a there is no getter for property name toString() error. I tried #{testEnum.display} and #{testEnum.name} both of them still inserts C whereas i want it to insert 2C. Do you guys know an easier way of handling this?
I don't want to change the model object to pass String rather than TestEnum because this object is being used in many places.Is there a way this can be done in the mybatis mapper without changing model object?
Thanks for your help :)
What you need is a TypeHandler
First, add a static method to your TestEnum to return a TestEnum given a display string:
public static TestEnum fromDisplay(String display){
for (TestEnum v : TestEnum.values()){
if (v.getDisplay().equals(display)){
return v;
}
}
return null;
}
Then use it to create your TypeHandler:
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
public class TestEnumTypeHandler extends BaseTypeHandler<TestEnum> {
#Override
public void setNonNullParameter(PreparedStatement ps, int i, TestEnum parameter, JdbcType jdbcType)
throws SQLException {
ps.setString(i, parameter.getDisplay());
}
#Override
public TestEnum getNullableResult(ResultSet rs, String columnName) throws SQLException {
return TestEnum.fromDisplay(rs.getString(columnName));
}
#Override
public TestEnum getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
return TestEnum.fromDisplay(rs.getString(columnIndex));
}
#Override
public TestEnum getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
return TestEnum.fromDisplay(cs.getString(columnIndex));
}
}
Finally, register your TypeHandler in your mybatis xml:
<typeHandlers>
<typeHandler handler="blah.blah.TestEnumTypeHandler "/>
</typeHandlers>
In addition to #Malt Answer:
The reason why what you are trying doesn't works it's the MyBatis EnumTypeHandler by default sets name() value of the method and is marked with final so you cannot override it:
EnumTypeHandler.class (Line 38 to 44):
#Override
public void setNonNullParameter(PreparedStatement ps, int i, E parameter, JdbcType jdbcType) throws SQLException {
if (jdbcType == null) {
ps.setString(i, parameter.name());
} else {
ps.setObject(i, parameter.name(), jdbcType.TYPE_CODE); // see r3589
}
}
Otherwise, the enum is created from the method valueOf(type, name) which also uses the name of the enum.
#Override
public E getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
String s = rs.getString(columnIndex);
return s == null ? null : Enum.valueOf(type, s);
}
#Override
public E getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
String s = cs.getString(columnIndex);
return s == null ? null : Enum.valueOf(type, s);
}
So definitely, you need to use a typeHandler specific to handle your enum which has a specific behaviour, but I would extends directly EnumTypeHandler in specific enum type handlers, instead of BaseTypeHandler (Malt answer), because you could reuse some functionality (not in your case, but maybe in others) so it handles a general enum behaviour.
You do not need to write any custom TypeHandler if you want to insert the value of your Enum.
The only one thing you need to do is to specify the getter method's name in your MyBatis insert.
Example:
SQL:
CREATE TABLE demo
(
id BIGINT,
value VARCHAR(10),
status CHAR(1)
);
MyBatis mapper:
#Update("UPDATE demo SET status = #{status.value} WHERE id= #{uuid}")
long updateStatus(#Param("status") Status status, #Param("uuid") String uuid);
And the Java Enum:
public enum Status {
ACTIVE("A"),
INACTIVE("I");
Status(final String value) {
this.value = value;
}
public String getValue() {
return value;
}
}
In your case you can use src = #{testEnum.display} in your SQL.
Related
I am unable to figure out how to send a list as a parameter to SQL Server Stored Procedure using myBatis
call sp(List<Object>)
I have a stored procedure inside a SQL Server(2012) which takes a parameter of type list.
CREATE TypeTable of Table
(
#FKId IN
#FKId INT
#FKId INT
#FKId INT
#FKId INT
#userName VARCHAR
)
My Stored Procedure call
ALTER PROCEDURE SP(#TypeTableList Typetable READONLY )
AS
BEGIN
/* My DB Operations To Enter New Records and Thier Child Records */
END
MyMapper
<select id="mapperId" parameterType="map" statementType="CALLABLE">
call sp(#{list})
</select>
POJO
public class ListClass {
private Long fk1;
private Long fk2;
private Long fk3;
private Long fk4;
private Long fk5;
private String userName;
public ListClass() {
super();
}
public Long getFk1() {
return fk1;
}
public void setFk1(Long fk1) {
this.fk1 = fk1;
}
public Long getFk2() {
return fk2;
}
public void setFk2(Long fk2) {
this.fk2 = fk2;
}
public Long getFk3() {
return fk3;
}
public void setFk3(Long fk3) {
this.fk3 = fk3;
}
public Long getFk4() {
return fk4;
}
public void setFk4(Long fk4) {
this.fk4 = fk4;
}
public Long getFk5() {
return fk5;
}
public void setFk5(Long fk5) {
this.fk5 = fk5;
}
public String getuserName() {
return userName;
}
public void setuserName(String userName) {
this.userName = userName;
}
}
I have tried using type handler of type array but i always get a exception.
I have not found any resources on creating a custom type handler for ArrayList With SQL Server
Any Help would be appriciated
Thankyou
The create statement you posted didn't work in SQL Server 2017, so I'll show you a simpler example.
DDLs:
create table Users (
id int,
name varchar(20)
);
create type UserTableType as table (
id int,
name varchar(20)
);
create procedure uspAddUsers
#UserTable UserTableType READONLY
as
begin
insert into Users (id, name)
select * from #UserTable
end;
POJO:
public class User {
private Integer id;
private String name;
// getter/setter
}
Mapper method:
#Options(statementType = StatementType.CALLABLE)
#Insert("{call uspAddUsers(#{users,typeHandler=pkg.UserListTypeHandler})}")
void insertUsers(#Param("users") List<User> users);
Note the typeHandler option.
As David Browne pointed out, the driver requires SQLServerDataType as the input, so you may need a type handler that converts the list into a SQLServerDataType.
The below is a simple type handler implementation.
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
import com.microsoft.sqlserver.jdbc.SQLServerDataTable;
import com.microsoft.sqlserver.jdbc.SQLServerPreparedStatement;
public class UserListTypeHandler extends BaseTypeHandler<List<User>>{
#Override
public void setNonNullParameter(PreparedStatement ps,
int i, List<User> parameter, JdbcType jdbcType)
throws SQLException {
SQLServerDataTable dataTable = new SQLServerDataTable();
dataTable.addColumnMetadata("id", java.sql.Types.INTEGER);
dataTable.addColumnMetadata("name", java.sql.Types.VARCHAR);
for (User user : parameter) {
dataTable.addRow(user.getId(), user.getName());
}
ps.unwrap(SQLServerPreparedStatement.class)
.setStructured(i, "UserTableType", dataTable);
}
// getNullableResult() won't be used
}
An executable demo tested with...
Microsoft SQL Server 2017 (RTM-CU15) (KB4498951) - 14.0.3162.1 (X64)
mssql-jdbc 7.3.1.jre8-preview
I have a current state where an enum MyType represent Type table with columns as:
ID
Name
And it's used to identify type using ID parameter with byId method:
public enum MyType {
FIRST_TYPE("First Type", 10),
SECOND_TYPE("Second Type", 20);
public static class Holder {
static Map<Integer, MyType > idMap = new HashMap<>();
private Holder() { }
}
private MyType(String name, Integer id) {
this.name = name;
this.id = id;
Holder.idMap.put(id, this);
}
public String getName() {
return name;
}
public static MyType byId(Integer id) {
return Holder.idMap.get(id);
}
My new requirement is to support also values exists in Type table, I found answers for dynamic enum, but accept answer is not to do it
No. Enums are always fixed at compile-time. The only way you could do this would be to dyamically generate the relevant bytecode.
What will be a better solution for finding also values (mainly IDs) from database (for example ID 30)
select ID from TYPE
Can I extends existing state instead of change it? can I add extra IDS from database using method?
EDIT
Even if I update as #StefanFischer suggested an interface which populate map with enum class and new database class, I still expect in code an enum return by byId method,
public interface MyType {
public static class Holder {
static Map<Integer, MyType> idMap = new HashMap<>();
private Holder() { }
}
public default void add(MyType myType, Integer id) {
Holder.idMap.put(id, myType);
}
public static MyType byId(Integer id) {
return Holder.idMap.get(id);
}
}
A distinct non-answer: you are trying to force yourself down the wrong rabbit hole.
The whole point of Enums are to give you certain advantages at compile time. At runtime, it really wouldn't matter to the JVM if you have a class with some final static Whatever fields, or an Enum with different constants. Or if you use an EnumSet versus an ordinary Set.
You use enums because they allow you to write down your source code in more elegant ways.
Therefore the approach of generating enums at runtime doesn't make sense.
The idea of enums is that you write source code using them. But when your enums are generated for you, how exactly would you write source code exploiting them?! As mentioned already, enum classes are final by default. You can't extend or enhance them separately. Whatever you would want to have, it needs to be generated for you. Which again raises the question: how would you exploit something at compile time, that gets generated at runtime?
Therefore, from a conceptual point of view, the approach outlined in the other answer (to use a Map) is a much better design point than trying to turn enums into something that they aren't meant to be.
If I understand it correctly the requirements are:
having a MyType.byId(Integer id) method that delivers some predefined values
it should be also extended dynamically from a Table Type from the database
So a enum can not be extended dynamically, but we could switch to a class.
So staying close to your code one could write something like:
import java.util.HashMap;
import java.util.Map;
public class MyType {
static Map<Integer, MyType> idMap = new HashMap<>();
static {
idMap.put(10, new MyType("First Type"));
idMap.put(20, new MyType("Second Type"));
}
private final String name;
private MyType(String name) {
this.name = name;
}
public String getName() {
return name;
}
public static MyType byId(Integer id) {
return idMap.get(id);
}
public static void addType(String name, Integer id) {
MyType lookup = byId(id);
if(lookup != null) {
if(!lookup.getName().equals(name)) {
System.out.println("conflicting redefinition for id " + id + ": '" + name + "' vs '" + lookup.name + "'");
//handle...
}
}
idMap.put(id, new MyType(name));
}
}
Test Data
Let's assume we have the following in the database:
stephan=# select * from Type;
id | name
----+-------------
30 | Third Type
10 | First Type
20 | Second Type
(3 rows)
So in the database we have the predefined types with id=10 and id=20 but also a type with id=30 that is not known per default to the application. But we can populate the types from the database.
Test Case
public static void main(String[] args) {
try {
Connection connection = createConnection();
try (connection) {
populateTypes(connection);
}
MyType type;
type = MyType.byId(10);
System.out.println(type.getName());
type = MyType.byId(20);
System.out.println(type.getName());
type = MyType.byId(30);
System.out.println(type.getName());
} catch (Exception e) {
e.printStackTrace();
}
}
JDBC Example
It doesn't matter what actual database technology is used to retrieve the values. Here an example for JDBC:
private static void populateTypes(Connection connection)
throws SQLException {
String sql = "SELECT * FROM type";
try (Statement st = connection.createStatement()) {
try (ResultSet rs = st.executeQuery(sql)) {
while (rs.next()) {
int id = rs.getInt("id");
String name = rs.getString("name");
MyType.addType(name, id);
}
}
}
}
Demo Output
First Type
Second Type
Third Type
Is that what you are looking for?
enum represents a group of constants (unchangeable variables, like final variables). you can not define it on runtime.
I'm coming from PHP and moved to java. I'm asking myself (and you guys) if there is a way to implement someting like this:
I'm trying to implement a class/classes to create CRUD operations for many database entities. All entities inherit their functions (most of them from the parent)
I need to implement the tableName and idFieldName in the parent class DatabaseEntity to avoid compiler warnings.
It seems like java tries to use the parents properties (which are obviously null) because the function is implemented in the parent.
Is there a way to overcome this problem? Any feedback is greatly apreciated!
abstract class DatabaseEntity {
protected String tableName;
protected String idFieldName;
public DataRecord readFromDB(int recordID) throws SQLException {
...
String sqlStatement = String.format("SELECT * FROM %s WHERE %s = %s", this.tableName, this.idFieldName, recordID); // Exception shows this line
...
}
}
class DatabaseRecord extends DatabaseEntity {
protected String tableName = "DatabaseRecordTable";
protected String idFieldName = "ID";
public void getRecord() {
...
DataRecord record = this.readFromDB(1); // leads to java.lang.NullPointerException: null
...
}
}
Disclaimer: I'm new to github and I apreciate any feedback on improoving my posts :)
when you use the method readFromDBin which you refer to the tableNameand the idFieldName, these two fields remain nullas long as they are not initailized,
try to remove the fields from your abstract class and do something like this :
abstract class DatabaseEntity {
// protected String tableName;
// protected String idFieldName;
public abstract String getTableName();
public abstract String getIdFieldName() ;
public DataRecord readFromDB(int recordID) throws SQLException {
...
String sqlStatement = String.format("SELECT * FROM %s WHERE %s = %s",getTableName(), getIdFieldName(), recordID);
...
}
}
and the implementation would be like :
class DatabaseRecord extends DatabaseEntity {
protected String tableName = "DatabaseRecordTable";
protected String idFieldName = "ID";
public void getRecord() throws SQLException {
DataRecord record = this.readFromDB(1);
}
#Override
public String getTableName() {
return this.tableName;
}
#Override
public String getIdFieldName() {
return this.idFieldName ;
}
}
I am developing a database application. Currently I am using java.sql combined with H2 embedded database. I would like to develop the Don't Repeat Yourself way.
So I set up a reuseable Database Row class and Database Property class as follows:
public class DatabaseProperty {
private String PropertyName;
private T Value;
private boolean Identifier;
public DatabaseProperty(String PropertyName, T Value, boolean identifier) {
this.PropertyName = PropertyName;
this.Value = Value;
this.Identifier = identifier;
}
public String getPropertyName() {
return PropertyName;
}
public T getValue() {
return Value;
}
public void setValue(T Value) {
this.Value = Value;
}
public boolean isIdentifier() {
return Identifier;
}
}
And...
public class DatabaseRow {
protected Connection DBConnection;
protected String TableName;
protected HashSet = new HashSet<>();
public DatabaseRow() //With all the above variables. Apologies for being lazy to type ;)
//Here's the problem part
//I'm trying to automatically generate an SQL Statement
//to check that the row specified by primary unique keys (ex:- Username and Password Combination for Log In)
public boolean existsInTable(){
try {
String SQL = "SELECT * FROM "+TableName+" WHERE ";
boolean addAND = false;
for(DatabaseProperty d:Columns) {
if(d.isIdentifier()) {
SQL+=(addAND?"AND ":"")+d.getPropertyName()+" = ? ";
addAND = true;
}
}
PreparedStatement ps = getDBConnection().prepareStatement(SQL);
And the code goes on...
The problem is that I do not have Generic based methods for setting parameters in PeparedStatement class. Instead there is setString(int index,String s), etc..
Please help me to overcome this..
Is there any object oriented wrappers available, like NotORM for PHP? Is there any trade off between performance and coding ease with such options?
Try to use this:
ps.setObject(index, object);
It should work in all cases where index is not null. I think it is not a problem for your case.
If object is null, then you need to set the type
ps.setObject(index, null, type);
The type you can get from the parameter metadata object:
ParameterMetaData meta=ps.getParameterMetaData();
int type = meta.getParameterType(index);
There is a library for JodaTime that provides Hibernate persistence. Recently I started looking at Joda-Money and started to see how that can be persisted using hibernate and I do not see any library.
Any suggestions?
Since the link to the example in Sudarshan's answer is broken, here is an implementation of a simple custom user type for org.joda.money.BigMoney, that persists money objects in two columns amount and currency) and an example of how to use it. It works the same for org.joda.money.Money.
package test;
import java.io.Serializable;
import java.math.BigDecimal;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Currency;
import org.apache.commons.lang.ObjectUtils;
import org.hibernate.HibernateException;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.type.StandardBasicTypes;
import org.hibernate.type.Type;
import org.hibernate.usertype.CompositeUserType;
import org.joda.money.BigMoney;
import org.joda.money.CurrencyUnit;
public class MoneyUserType implements CompositeUserType
{
private static final String[] PROPERTY_NAMES = {"amount", "currencyUnit"};
private static final Type[] PROPERTY_TYPES = {StandardBasicTypes.BIG_DECIMAL, StandardBasicTypes.CURRENCY};
public MoneyUserType()
{
super();
}
public Object assemble(final Serializable cached, final SessionImplementor session, final Object owner)
throws HibernateException
{
return cached;
}
public Serializable disassemble(final Object value, final SessionImplementor session) throws HibernateException
{
return (Serializable) value;
}
public String[] getPropertyNames()
{
return PROPERTY_NAMES.clone();
}
public Type[] getPropertyTypes()
{
return PROPERTY_TYPES.clone();
}
public Object getPropertyValue(final Object component, final int property) throws HibernateException
{
BigMoney money = (BigMoney) component;
return (property == 0) ? money.getAmount() : money.getCurrencyUnit().toCurrency();
}
public Object nullSafeGet(final ResultSet rs, final String[] names, final SessionImplementor session,
final Object owner) throws HibernateException, SQLException
{
BigDecimal amount = StandardBasicTypes.BIG_DECIMAL.nullSafeGet(rs, names[0], session);
Currency currency = StandardBasicTypes.CURRENCY.nullSafeGet(rs, names[1], session);
return BigMoney.of(CurrencyUnit.of(currency), amount);
}
public void nullSafeSet(final PreparedStatement st, final Object value, final int index,
final SessionImplementor session) throws HibernateException, SQLException
{
BigMoney money = (BigMoney) value;
BigDecimal amount = (money == null) ? null : money.getAmount();
Currency currency = (money == null) ? null : money.getCurrencyUnit().toCurrency();
StandardBasicTypes.BIG_DECIMAL.nullSafeSet(st, amount, index, session);
StandardBasicTypes.CURRENCY.nullSafeSet(st, currency, index + 1, session);
}
public Object replace(final Object original, final Object target, final SessionImplementor session,
final Object owner) throws HibernateException
{
return deepCopy(original);
}
public void setPropertyValue(final Object component, final int property, final Object value)
throws HibernateException
{
throw new HibernateException("Money is immutable.");
}
public Object deepCopy(final Object value) throws HibernateException
{
return (value != null) ? BigMoney.of(((BigMoney) value).getCurrencyUnit(),
((BigMoney) value).getAmount()) : null;
}
public boolean equals(final Object x, final Object y) throws HibernateException
{
return ObjectUtils.equals(x, y);
}
public int hashCode(final Object x) throws HibernateException
{
return ObjectUtils.hashCode(x);
}
public boolean isMutable()
{
return false;
}
public Class<?> returnedClass()
{
return BigMoney.class;
}
}
Usage:
#Type(type = "test.MoneyUserType")
#Columns(columns = {#Column(name = "AMOUNT"), #Column(name = "CURRENCY")})
private BigMoney money;
The User Type project includes Joda Money support.
The User Type project provides support for joda-money 0.6 since version 3.0.0. Please note however that this requires Hibernate 4. Also the current joda-money version is 0.8
If you want to use it with Hibernate 3 use the example in Sudarshan anwser (it's bugged at the time of writing).
Okay I took your advice and cooked up a custom type for Money as defined in the Joda Library, as a reference people can look it up here,usage here and test for the custom type here
Based on http://jadira.sourceforge.net
Money Types typically consist of a currency and amount. Jadira makes it possible to store only the amount to the database with the currency configured using a parameter. For example:
#Column
#Type(type = "org.jadira.usertype.moneyandcurrency.joda.PersistentMoneyAmount",
parameters = {#org.hibernate.annotations.Parameter(name = "currencyCode", value = "USD")})
private Money money;
Alternatively, with other types two columns to hold the amount an currency:
#Columns(columns = { #Column(name = "MY_CURRENCY"), #Column(name = "MY_AMOUNT") })
#Type(type = "org.jadira.usertype.moneyandcurrency.joda.PersistentMoneyAmountAndCurrency")
private Money money;
Joda-Money's very new, so it's no surprise that noone's provided a Hibernate mapping for it yet.
However, writing custom Hibernate type adapters is pretty straightforward. If you look at the source for the JodaTime adapters, you'll see they're really simple. See the docs for how to write your own.