views:

671

answers:

6

In legacy database tables we have numbered columns like C1, C2, C3, C100 or M1, M2, M3, M100.
This columns represent BLOB data.

It is not possible to change anything it this database.

By using JPA Embeddable we map all of the columns to single fields. And then during embedding we override names by using 100 override annotations.

Recently we have switched to Hibernate and I've found things like UserCollectionType and CompositeUserType. But I hadn't found any use cases that are close to mine.

Is it possible to implement some user type by using Hibernate to be able to map a bundle of columns to a collection without additional querying?

Edit:
As you probably noticed the names of columns can differ from table to table. I want to create one type like "LegacyArray" with no need to specify all of the @Columns each time I use this type. But instead I'd use

  @Type(type = "LegacyArrayUserType",
        parameters =
   {
      @Parameter(name = "prefix", value = "A"),
      @Parameter(name = "size", value = "128")
   })
   List<Integer> legacyA;

  @Type(type = "LegacyArrayUserType",
        parameters =
   {
      @Parameter(name = "prefix", value = "B"),
      @Parameter(name = "size", value = "64")
   })
   List<Integer> legacyB;
A: 

You can use UserTypes to map a given number of columns to any type you wish. This could be a collection if (for example) for collections are always bounded in size by a known number of items.

It's been a while (> 3 years) since I used Hibernate so I'm pretty rusty but I recall it being very easy to do; your BespokeUserType class gets passed the ResultSet to hydrate your object from it.

oxbow_lakes
Thanks for the answer. Could you please read "edit" section and confirm that it is possible?
Mykola Golubyev
+1  A: 

Personally, I think that design sounds like it breaks first normal form for relational databases. What happens if you need C101 or M101? Change your schema again? I think it's very intrusive.

If you add Hibernate to the mix it's even worse. Adding C101 or M101 means having to alter your Java objects, your Hibernate mappings, everything.

If you have 1:m relationships with C and M tables, you'd be able handle the cases I just cited by adding additional rows. Your Java objects contain Collection<C> or Collection<M>. Your Hibernate mappings are one-to-many that don't change.

Maybe the reason that you don't see any Hibernate examples to match your case because it's a design that's not recommended.

If you must, maybe you should look at Hibernate Component Mapping.

UPDATE: The fact that this is legacy is duly noted. My point in bringing up first normal form is as much for others who might find this question in the future as it is for the person who posted the question. I would not want to answer the question in such a way that it silently asserted this design as "good".

Pointing out Hibernate component mapping is pertinent because knowing the name of what you're looking for can be the key when you're searching. Hibernate allows an object model to be finer grained than the relational model it maps. You are free to model a denormalized schema (e.g., Name and Address objects as part of a larger Person object). That's just the name they give such a technique. It might help find other examples as well.

duffymo
I think you miss the point of the question. He didn't say this was a good or new design. He said it was a legacy design.I, too, had to code for something like this. I ended up creating an @Embeddable that just implements Collection and Iterable. But I think I'll go back and re-implement it as a UserType that maps to a core Collection. The only problem with custom UserTypes is that there isn't good documentation on how to implement them effitiently. i.e. hashcode and other comparison behavior with hibernate to avoid updated on no 'effective' change.
DragonFax
I don't think I did. I noted "legacy". The Hibernate component mapping allows you to take any number of columns in a table, encapsulate them in an object (e.g., Name in the example), and make them member data in a larger object. I've implemented UserTypes and found the documentation for hashcode and such to be fine. They should obey "identity semantics".
duffymo
Thanks for answer. Of course it sounds like a bad design. If we just could change the schema. Though I am not sure there is something related to 1st normal form. There is no repeating groups. All those N columns are like BLOB. Anyway, could you read "edit" section and confirm that it is possible?
Mykola Golubyev
+1  A: 

Sorry if I'm misunderstanding your problem here, I don't know much about Hibernate. But couldn't you just concatenate during selection from database to get something like what you want?

Like:

SELECT whatever
     , C1||C2||C3||C4||...||C100 AS CDATA
     , M1||M2||M3||M4||...||M100 AS MDATA
FROM ...
WHERE ...

(Of course, the concatenation operator differs between RDBMSs.)

Kip
I Need mappings to the Entity and not just select statement.
Mykola Golubyev
+3  A: 

I can think of a couple of ways that I would do this.

1. Create views for the collection information that simulates a normalized table structure, and map it to Hibernate as a collection:

Assuming your existing table is called primaryentity, I would create a view that's similar to the following:

-- untested SQL...
create view childentity as
(select primaryentity_id, c1 from primaryentity union
select primaryentity_id, c2 from primaryentity union
select primaryentity_id, c3 from primaryentity union
--...
select primaryentity_id, c100 from primaryentity)

Now from Hibernate's perspective, childentity is just a normalized table that has a foreign key to primarykey. Mapping this should be pretty straight forward, and is covered here:

The benefits of this approach:

  • From Hibernate's point of view, the tables are normalized, it's a fairly simple mapping
  • No updates to your existing tables

The drawbacks:

  • Data is read-only, I don't think your view can be defined in an updatable manner (I could be wrong)
  • Requires change to the database, you may need to create lots of views

Alternately, if your DBA won't even let you add a view to the database, or if you need to perform updates:


2. Use Hibernate's dynamic model mapping facility to map your C1, C2, C3 properties to a Map, and have some code you your DAO layer do the appropriate conversation between the Map and the Collection property:

I have never done this myself, but I believe Hibernate does allow you to map tables to HashMaps. I'm not sure how dynamically Hibernate allows you to do this (i.e., Can you get away with simply specifying the table name, and having Hibernate automatically map all the columns?), but it's another way I can think of doing this.

If going with this approach though, be sure to use the data access object pattern, and ensure that the internal implementation (use of HashMaps) is hidden from the client code. Also be sure to check before writing to the database that the size of your collection does not exceed the number of available columns.

The benefits of this approach:

  • No change to the database at all
  • Data is updatable
  • O/R Mapping is relatively simple

The drawbacks:

  • Lots of plumbing in the DAO layer to map the appropriate types
  • Uses experimental Hibernate features that may change in the future
Jack Leow
The problem is I don't want to specify all the names by my own.
Mykola Golubyev
Perhaps I wasn't clear in my response, but I don't think you would in either case. Which option were you looking at?
Jack Leow
"...map your C1, C2, C3 properties to a Map...". For doing that I have to specify all column names, or I missed something?
Mykola Golubyev
Sorry, I think you may be right, let me have a look at the Hibernate documentation to verify. Are views out of the question? Incidentally, I just realized you can create updatable/insertable views, which may get you around the read-only problem.
Jack Leow
Probably we could switch to Views, but it would me much much greater to solve this with smart mappings.
Mykola Golubyev
Your answer will be accepted automatically. And actually View-solution is a solution.
Mykola Golubyev
+1  A: 

[EDIT] I suggest to use a CompositeUserType. Here is an example. There is also a good example on page 228f in the book "Java Persistence With Hibernate".

That allows you to handle the many columns as a single object in Java.

The mapping looks like this:

@org.hibernate.annotations.Columns(columns = {
    @Column(name="C1"),
    @Column(name="C2"),
    @Column(name="C3"),
    ...
})
private List<Integer> c;

Hibernate will load all columns at once during the normal query.

In your case, you must copy the int values from the list into a fixed number of columns in nullSafeSet. Pseudocode:

for (int i=1; i<numColumns; i++)
    if (i < list.size())
        resultSet.setInt(index+i, list.get(i));
    else
        resultSet.setNull(index+i, Hibernate.INTEGER.sqlType());

In nullSafeGet you must create a list and stop adding elements when a column is NULL. For additional safety, I suggest to create your own list implementation which doesn't allow to grow beyond the number of columns (inherit from ArrayList and override ensureCapacity()).

[EDIT2] If you don't want to type all the @Column annotations, use a code generator for them. That can be as simple as script which you give a name and a number and it prints @Column(...) to System.out. After the script ran, just cut&paste the data into the source.

The only other solution would be to access the internal Hibernate API to build that information at runtime but that API is internal, so a lot of stuff is private. You can use Java reflection and setAccessible(true) but that code probably won't survive the next update of Hibernate.

Aaron Digulla
POJO should be left as POJO. I don't like the Idea to generate domain objects. I just one normal way of legacy encapsulation.
Mykola Golubyev
A POJO is a POJO no matter if you wrote it or if you had the computer write it for you. Are you an artisan or a software developer? Automate what you can to be more efficient.
Aaron Digulla
What "... generate abstract base classes which you derive from..." is this about? I don't want a business object to be derived from some generated interface (That's why I mentioned POJO).
Mykola Golubyev
Sorry, I misunderstood your original questions. Please let me know if anything is still unclear.
Aaron Digulla
What you suggest is kind of we are doing right now. The problems is you have to specify all 100 columns using @Columns. We use @Embeddable but in any case we have to specify all of the columns.
Mykola Golubyev
You can't get that for free. See my EDIT2.
Aaron Digulla
A: 

I too have never used Hibernate.

I suggest writing a small program in an interpreted language (such as Python) in which you can execute a string as if it were a command. You could construct a statement which takes the tedious work out of doing what you want to do manually.

Steven
I need Java object that is mapped to the Table. Can't see how python can be embedded to solve such things.
Mykola Golubyev