Setting column length of a Long value with JPA annotations - mysql

I'm performing a little database optimisation at the moment and would like to set the column lengths in my table through JPA. So far I have no problem setting the String (varchar) lengths using JPA as follows:
#Column(unique=true, nullable=false, length=99)
public String getEmail() {
return email;
}
However, when I want to do the same for a column which is of type Long (bigint), it doesn't work. For example, if I write:
#Id
#Column(length=7)
#GeneratedValue(strategy = GenerationType.AUTO)
public Long getId() {
return id;
}
The column size is still set as the default of 20. Are we able to set these lengths in JPA or am I barking up the wrong tree?
Thanks,
Gearoid.

precision, scale make more sense for a numeric type. Also depends whether the JDBC driver and RDBMS allows setting of those on the particular column type

Are you using Eclipse as your IDE? If so, I suggest you make use of the Dali plugin (already installed) and active the "JPA Details" view. This view will help guide you as to what attributes you need to plug into your annotations.
You could have scale=7 instead of length=7. However JPA is still going to tell the database to prepare to hold an int of scale 20.
Also make sure you have your database dialect properly set.

Related

JPA Hibernate - Multiple Database Dialects and nvarchar(length) data type

I have to do a project using JPA + Hibernate in which I'm using 3 dialects: MySQL5InnoDBDialect, MSSQL2012Dialect and Oracle12cDialect.
Right now I have a specification which is telling me that for some column from:
Oracle database, I have to use NVARCHAR2(LENGTH) data type
MySql database, I have to use VARCHAR(LENGTH) data type
MSSQL database, I have to use NVARCHAR(LENGTH) data type
... and here is my problem..
If I use:
#Column(name="columnName" length = 255)
private String columnName;
hibernate generates varchar(255) and this is good just for MySQL
If I use:
#Column(name="columnName", columnDefinition="nvarchar2(255)")
private String columnName;
it's not possible in MySQL, i get error because of columnDefinition, but in oracle is okay
I tried to customize MySQL dialect creating
public class CustomMySQL5InnoDBDialect extends MySQL5InnoDBDialect{
public CustomMySQL5InnoDBDialect() {
super();
registerColumnType(Types.NVARCHAR, "nvarchar2($l)");//$l not $1
registerHibernateType(Types.NVARCHAR, StandardBasicTypes.STRING.getName());
}
}
and giving this class in hibernate configuration for MySQL dialect.
I have the same problem in MySQL if I'm using columnDefinition property.
Can you help with this problem please?
The solution is to make use of the feature that the JPA API spec provides you with for just this situation. Define a file orm.xml for each datastore that you need to support, and enable the requisite one when using each database. See this link for details of the file format. That way you don't need to think about hacking the internal features of whichever JPA provider you are using, and you also retain JPA provider portability, as well as database portability
The idea of putting schema specific information info (static) Java annotations is an odd one, even more so when wanting database portability.

JPA mapping failing on strange message

I have a strange problem where the JPA mapping is failing on converting to a timestamp, but the value it's using appears to be the entire row, not just one variable.
The error is:
java.sql.SQLException: Value '1988├╗ ├╗├╗├╗├╗├╗├╗0
07 1234567 wk├╗0├╗├╗├╗├╗├╗ ' can not be represented as java.sql.Timestamp
where the value seems to be the entire row, with most of the bad characters being nulls. Debug logging isn't giving me much at the moment, and I'm not sure whether it's an error in my mapping class, collation issues, or something else.
MySQL workbench reads all the information from the table correctly. Running mysql from the command outputs all the data correctly. Neither show any special characters anywhere.
A simplified version of my mapping class is:
#Entity
#Audited
#JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
public class PersonSundry {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#NotNull
#OneToOne(fetch=FetchType.LAZY)
#JoinColumn(name="person_id", unique=true)
private Person person;
#ManyToOne(fetch=FetchType.EAGER)
private Lookup1 lookup1;
#ManyToOne(fetch=FetchType.EAGER)
private Lookup2 lookup2;
#Lob
private String backgroundInfo;
private LocalDate dateOfSomething1;
private LocalDate dateOfSomething2;
private LocalDate dateOfSomething3;
// getters and setters
}
Has anyone come across this before? Any ideas where else to look?
EDIT: The root cause turned out to be a generic failed cast of 00-00-0000 to a timestamp, however I'm going to leave the question open to see if someone knows where the strange error message was given instead of an exact one.
Are you using MYSQL?
Please try using this type of connection string.
jdbc:mysql://yourserver:3306/yourdatabase?zeroDateTimeBehavior=convertToNull
Datetimes with all-zero components (0000-00-00 ...) — These values can not be represented reliably in Java. Connector/J 3.0.x always converted them to NULL when being read from a ResultSet.
Connector/J 3.1 throws an exception by default when these values are encountered as this is the most correct behavior according to the JDBC and SQL standards. This behavior can be modified using the zeroDateTimeBehavior configuration property. The allowable values are:
exception (the default), which throws an SQLException with an SQLState of S1009.
convertToNull, which returns NULL instead of the date.
round, which rounds the date to the nearest closest value which is 0001-01-01.

How do I use full text search with MySql and Entity Framework 4.2 Code First?

I am using Entity Framework 4.2 Code First and MySQL. I need to be able to do a full text search across several fields of my table. However, I am having a number of issues with this:
1) Full text search under MySQL requires the MyISAM table type. However, when I run the DropCreateDatabaseAlways initializer, the table type I get is InnoDB. Here is an example of my table definition:
[Table("Patient")]
[Serializable]
public class Patient
{
public Patient()
{
}
[Key]
public int PatientID { get; set; }
[MaxLength(50)]
public string FirstName { get; set; }
[MaxLength(50)]
public string LastName { get; set; }
}
Is it possible to specify MyISAM table type? If so, how?
2) I need to specify the full text index:
ALTER TABLE `patient` ADD FULLTEXT INDEX `Name`(`FirstName`, `LastName`);
One possibility I can think of is:
public class MyDbInitializer : DropCreateDatabaseAlways<MyDbContext>
{
protected override void Seed(MyDbContext context)
{
context.Database.ExecuteSqlCommand(
"ALTER TABLE `patient` ADD FULLTEXT INDEX `Name`(`FirstName`, `LastName`)");
}
}
Is there a better way?
3) I need to be able to search using the full text index. Ideally in a way that works with my existing linq to SQL.
One less-then-desirable possiblity I can think of is to create a stored procedure (again, using ExecuteSqlCommand) that takes a string and returns either a list of matching PatientIDs or a list of Patient rows. For example:
IEnumerable<int> patientIDs = [...call stored proc to get matching ids...]
var patients = from p in patients
where patientIDs.Any(pid => pid == p.PatientID)
select new {...}
Or:
var patients = from p in [...call stored proc to get matching patient records...]
select new {...}
How would I do this? Is there a better way?
4) Bonus question: Even though I specify the [MaxLength(50)] attribute, my strings are being stored as MEDIUMTEXT instead of VARCHAR(50) as I would expect. Without the [MaxLength] attribute I get LONGTEXT. How do I specify VARCHAR(n) for strings types?
Any ideas?
Thanks in advance,
Dan
It is provider specific so unless your provider for MySQL offers some way to change it you will not be able to do that from code first. I think you should be able to run ALTER TABLE in custom DB initializer to change the engine.
If you use migration feature (EF 4.3 and newer) you can script the index in the migration otherwise you need to use your initializer.
You need to query the database with SQL. EF has no support for fulltext search queries. To call a stored procedure you must use SqlQuery and populate a type with same properties as the result set.
Try to use [Column(TypeName="VARCHAR(50)] annotation.
If you are a dotConnect for MySQL user, we advise you to take a look at this article. If you use some other provider, we advise you to contact its vendor.

Tell Hibernate's hbm2ddl to add MySQL enum columns for #Enumerated annotated fields

I'm creating a DB table using hbm2ddl with Java code similar to the following:
#Entity
public class Filter {
public enum Type {
TypeA, TypeB;
}
#Enumerated(EnumType.STRING)
private Type type;
}
It works fine, but for "type" a VARCHAR column is created, i.e. the DDL code looks like this:
CREATE TABLE IF NOT EXISTS `filter` (`type` varchar(255) DEFAULT NULL)
But what I want to have is this:
CREATE TABLE IF NOT EXISTS `filter` (`type` enum('TypeA','TypeB') NOT NULL)
Is this possible to declare in Hibernate, preferred with annotations?
Or is there a way to extend SchemaUpdate and overwrite the method that renders the alter script part for enumerated field the way I like it?
Background: The same database is used in a PHP part of the project and I want to prevent that invalid values are inserted.
Although it seems there is no way to handle MySQL enums 100% automatically, as pointed out by Lucas on his answer, there is actually a simple way to contour it. You may use columnDefinition attribute on #Column annotation, which seems to be specifically designed to generate custom DDL code.
See the documentation excerpt describing the attribute:
(Optional) The SQL fragment that is used when generating the DDL for the column.
Defaults to the generated SQL to create a column of the inferred type.
The NOT NULL restriction is quite standard, and is supported by another attribute nullable.
Thus, your property definition would look like this:
#Enumerated(EnumType.STRING)
#Column(columnDefinition = "enum ('TypeA', 'TypeB')", nullable = false)
private Type type;
I believe that's going to be complicated, since the java.sql.Types, which define the sql types treated by java, does not have enum type (since it's not a standardized type according to SQL-92).
If that was the case you could create a hibernate custom UserType extending the EnumType and setting the sqlType accordingly, but since java.sql.Types doesn't handle it I don't see how to use native sql enum.
best regards!

Hibernate database specific columnDefinition values

the problem is as follows: We're using hibernate with annotations as O/R Mapper.
Some #Column annotations look like:
#Column(columnDefinition = "longblob", name = "binaryData", nullable = true)
or
#Column(columnDefinition = "mediumtext", name = "remark", nullable = true)
with the columnDefinition attributes being mysql specific
on postgres for example, the columnDefinition values should be "bytea" and "varchar(999999)"
and on oracle probably something else.
Problems arise currently at the time of Schema Export, e.g. when creating the DDL statements.
Possible workarounds that I can think of are
- Hack some JDBC driver that does a text replace (e.g. longblob->bytea) for the DDL statements. That's ugly but will work somehow
- Use hibernate xml configuration instead of annotations. That will probably work but I prefer annotations
Does anybody know any alternatives? Hibernate specific workarounds are ok, e.g. if the columnDefinition attribute can contain dialect specific values like
#Column(columnDefinition = "mysql->mediumtext, postgres->varchar(999999)", name = "remark", nullable = true)
Thanks
Holger
Why don't you use the database-agnostic annotations like:
#Lob (on a byte[] or a String property)
#Column(length=90000) (on a String property)
and see what columns will be generated in the database. They will most likely be of the types you want them to be.
Some ideas:
Use annotation in general, but overload them from Xml in the case where they are specific to your database. Then you can have one configuration file specific to your database.
Use java Constants in your annotations (they have to be compile-time constants, so you are limited). You can have several sets of Java constants, and point toward the one you want to export to. (Beware, when you point toward another constant, you have to recompile everything.)
I have also used the dialect to switch some code in my Configuration class. The configuration class receives all data (from annotations or xml), and can then postprocess it.
For example, I have changed the concatenation symbol from '||' on Oracle to '+' on SqlServer.
This is conveniently done at runtime :-)