How can Hibernate map the SQL data-type nvarchar(max)? - sql-server-2005

I have a column in my SQL-2005 database that used to be a varchar(max), but it has been changed to an nvarchar(max).
Now I need to update my hibernate mapping file to reflect the change, and this is what it used to be:
<element type="text" column="Value"/>
When I try to run the application, the following error appears:
org.hibernate.HibernateException: Wrong column type in [Table] for column Value. Found: ntext, expected: text
What should I put in the 'type' attribute to correctly map the column as an nvarchar(max)?
I've tried setting the type to ntext, but hibernate didn't know what that was. I tried setting the type to string, but it treated string as a text type.

What worked for me is to put the actual column definition in a #Column annotation:
#Column(name="requestXml", columnDefinition = "ntext")
private String request;

It can be fixed by
#Nationalized
annotation from hibernate, that marks a character data type like a nationalized variant (NVARCHAR, NCHAR, NCLOB, etc).

Found the answer at Tremend Tech Blog. You have to write your own SQLServerDialect class, it looks something like this:
public class SQLServerNativeDialect extends SQLServerDialect {
public SQLServerNativeDialect() {
super();
registerColumnType(Types.VARCHAR, "nvarchar($l)");
registerColumnType(Types.CLOB, "nvarchar(max)");
}
public String getTypeName(int code, int length, int precision, int scale) throws HibernateException {
if(code != 2005) {
return super.getTypeName(code, length, precision, scale);
} else {
return "ntext";
}
}
}
This class maps Hibernate's types to SQL types, so the class will map the nvarchar(max) SQL Data Type to Hibernate's CLOB data type.
The getTypeName method is used to return "ntext" when Hibernate asks about the data type with code 2005 (which looks like it's the nvarchar(max) data type).
Finally, you need to change your hibernate persistence dialect to this new SQLServerDialect class, which allows hibernate to translate data types into SQL data types.

I think it is
registerColumnType(Types.VARCHAR, "nvarchar($l)"); // l like _l_ength, not 1.

Using #Nationalized with length = Integer.MAX_VALUE should resolve the issue of NVARCHAR(MAX).
The entity class should somewhat like this
#Nationalized
#Column(length = Integer.MAX_VALUE)
private String nvarcharMax;
For more details checkout the documentation here

Related

JDBC querying and inserting into arbitrary tables

What is a good way to query (always selecting all columns) and insert into arbitrary Oracle database tables using JDBC? I created the following method (haven't tested it yet) for retrieving any table's attribute names and types (I need the names for display purposes):
public ArrayList<Map<String, String>> retrieveTableAttributes(String tableName) throws SQLException{
ArrayList<Map<String, String>> attributes = new ArrayList<>();
DatabaseMetaData dbmd = conn.getMetaData();
ResultSet resultSet = dbmd.getColumns(null, null, tableName, null);
int i = 0;
while (resultSet.next()) {
attributes.add(new HashMap<String, String>());
attributes.get(i).put(resultSet.getString("COLUMN_NAME"), resultSet.getString("TYPE_NAME"));
i++;
}
resultSet.close();
return attributes;
}
I found this method PreparedStatement#setObject(int parameterIndex, Object x, int targetSqlType) which I think can be used to set a value for any arbitrary column type (in this case I would need to get the column type as an int instead of the type name), but I am not sure about this. So, is there a better way for setting and more importantly getting values from columns with arbitrary types? If it helps, I am trying to create a Java EE GUI tool for manipulating a database (adding, deleting, updating rows from any table in the database).
A simple method to get the type code from a named type using reflection:
public int getTypeByName(String name) {
for (Field field : java.sql.Types.class.getFields()) {
if (name.equalsIgnoreCase(field.getName())) return (Integer) field.get(null);
}
return -1;
}
Note that the TYPE_NAME column is probably not guaranteed to match Oracle's column support. A safer bet might just be to rely on the driver to make the right conversion choices using the untyped setObject and getObject methods.

Hibernate SQL transformation fails for Enum field type

I am using a SQL query and then transforming the result using Hibernates's Transformers.aliasToBean().
One of the columns in my query is an enum. The transformation somehow fails for the enum. What should I do? Which datatype should I use? I want more than 1 character to transform the result into my enum type.
This is how the simplified version of my query/code looks like (b is an enum in the table profiles):
session.createSQLQuery("select a, b from profiles").setResultTransformer(Transformers.aliasToBean(Profile.class))
.list();
Exception : expected type: Foo.ProfileStateEnum, actual value: java.lang.Character
Assuming that the java enum type that corresponds to column b is Foo.ProfileStateEnum, the following code snippet should work for you. (I tested with Hibernate 4.1.6)
import java.util.Properties;
import org.hibernate.type.Type;
import org.hibernate.type.IntegerType;
import org.hibernate.internal.TypeLocatorImpl.TypeLocatorImpl;
import org.hibernate.type.TypeResolver.TypeResolver;
import org.hibernate.type.EnumType;
Properties params = new Properties();
params.put("enumClass", "Foo.ProfileStateEnum");
params.put("type", "12"); /*type 12 instructs to use the String representation of enum value*/
/*If you are using Hibernate 5.x then try:
params.put("useNamed", true);*/
Type myEnumType = new TypeLocatorImpl(new TypeResolver()).custom(EnumType.class, params);
List<Profile> profileList= getSession().createSQLQuery("select a as ID, b from profiles")
.addScalar("ID", IntegerType.INSTANCE)
.addScalar("b", myEnumType )
.setResultTransformer(Transformers.aliasToBean(Profile.class))
.list();
I found two ways to achieve it.
Use org.hibernate.type.CustomType with org.hibernate.type.EnumType(put either EnumType.NAMED or EnumType.TYPE, see EnumType#interpretParameters). Like below:
Properties parameters = new Properties();
parameters.put(EnumType.ENUM, MyEnum.class.getName());
// boolean or string type of true/false; declare database type
parameters.put(EnumType.NAMED, true);
// string only; declare database type
parameters.put(EnumType.TYPE, String.valueOf(Types.VARCHAR));
EnumType<MyEnum> enumType = new EnumType<>();
enumType.setTypeConfiguration(new TypeConfiguration());
enumType.setParameterValues(parameters);
CustomType customEnumType = new CustomType(enumType);
Another simple way. Use org.hibernate.type.StandardBasicTypeTemplate with org.hibernate.type.descriptor.sql.*TypeDescriptor. Like below:
StandardBasicTypeTemplate<MyEnum> enumType =
new StandardBasicTypeTemplate<>(VarcharTypeDescriptor.INSTANCE,
new EnumJavaTypeDescriptor<>(MyEnum.class));
Let's see why you are getting this exception.
From the question it is obvious that you have used #Enumerated(EnumType.STRING) annotation for the field 'b' in you model class. So the field is an enum for your model class and a varchar for your database. Native SQL is not concerned about you model class and returns what ever is there in the database table as it is. So in your case, the SQLQuery you are using will return a String for 'b' instead of a ProfileStateEnum type. But your setter method for 'b' in the Profile class takes a ProfileStateEnum type argument.
Thus you get the exception "expected type: Foo.ProfileStateEnum, actual value: java.lang.Character"
You can use Aliasing to solve the problem.
What I suggest is, alias your column with any name you want and create a setter method for that alias in your model/dto.
For example, lets alias your column as 'enumStr'.
Then your query will look like this : "select a, b as enumStr from profiles"
Now, create a setter method for that alias in the your Profile class.
(Assuming that the enum ProfileStateEnum can have any of the two values STATE1 and STATE2)
public void setEnumStr(String str){
/*Convert the string to enum and set the field 'b'*/
if(str.equals(ProfileStateEnum.STATE1.toString())){
b = ProfileStateEnum.STATE1;
} else {
b = ProfileStateEnum.STATE2;
}
}
Now on transforming, the setter for the alias setEnumStr(String) will be invoked instead of setter for the field setB(ProfileStateEnum) and the string will be converted and saved to the type you want without any exceptions.
I am a beginner in Hibernate and the solution worked for me. I am using PostgreSQL. But I believe it works for other databases too.

C# Linq SQL save List<Point>

I have a class Point:
class Point
{
public int X { get; set; }
public int Y { get; set; }
public double Value { get; set; }
}
and class Chart which aggregates cca 1000 points.
[Table(Name = "Chart")]
class Chart
{
public List<Point> Points { get; set; }
}
I want to efficiently save Points to database. I don't need to do any operation on top of Points. Is there a way how to save the points in a single column? I think this way should be more efficient than creating new Table for 1000000 points. Thx
Look up SqlBulkCopy - it's not Linq, but it allows you to push masses of data into SQL Server much more quickly than Linq would.
I would recommend using a table. 1000000 rows of two ints, a double and an FK is a going to be pretty darn efficient. If you're worried about getting the data to and from the database perhaps doing something other than linq to sql is required for this data
That said you could use an XML type column and store your points in there, which is supported by Linq to sql. But not without some other considerations.
From the docs
The SQL Server XML data type is
available starting in Microsoft SQL
Server 2005. You can map the SQL
Server XML data type to XElement,
XDocument, or String. If the column
stores XML fragments that cannot be
read into XElement, the column must be
mapped to String to avoid run-time
errors. XML fragments that must be
mapped to String include the
following:
A sequence of XML elements
Attributes
Public Identifiers (PI)
Comments
Although you can map XElement and
XDocument to SQL Server as shown in
the Type Mapping Run Time Behavior
Matrix, the DataContext.CreateDatabase
method has no default SQL Server type
mapping for these types.

Find Underlying Column Size Via NHibernate Metadata

Is there a way to use SessionFactory.GetClassMetadata(), or any other method you're aware of, to dynamically get the maximum size of a varchar column that underlies an NHibernate class' string property?
To clarify, I'm not looking to read a length attribute that's specified in the NHibernate mapping file. I want to deduce the actual database column length.
See the code below for two different ways you can get the column size for a string from NHib metadata.
Cheers,
Berryl
[Test]
public void StringLength_DefaultIs_50_v1()
{
_metadata = _SessionFactory.GetClassMetadata(typeof(User));
var propertyType = _metadata.GetPropertyType("Email") as StringType;
Assert.That(propertyType.SqlType.Length, Is.EqualTo(50));
}
[Test]
public void StringLength_DefaultIs_50_v2()
{
var mapping = _Cfg.GetClassMapping(typeof(User));
var col = mapping.Table.GetColumn(new Column("Email"));
Assert.That(col.Length, Is.EqualTo(50));
}
When the Session factory is generated the NH engine does not check (and retrieve) what the underlying database is. For your case either you provide a "rich" mapping to have everything available at runtime, OR make a function that reads the necessary information from the DB (ie select * from sys.columns ..... for sql-server) when you need it.
Mind you that a rich mapping also allows the NH engine to make some automations (like checking if the size of the string passed is larger than the length of the (n)varchar column)

How to setup a column that is an enumeration with Nhibernate?

using nhibernate, how would I setup a column to be an enumeration type?
e.g.
User class has UserType which is an enumeration
public enum UserType { Normal = 1, Super = 2 }
You don't build a map for the enum. You add the enum to a map on an object. Declare the type as the type of the enum. The column in the db needs to be an int, and NHib will map it properly.
See here in my answer.