Hibernate Lesen aus anderer Tabelle

OlliL

Bekanntes Mitglied
Hallo,

folgendes Datenmodell sei gegeben:

Code:
mysql> desc capitalsources;
+-----------------+---------------------+------+-----+------------+----------------+
| Field           | Type                | Null | Key | Default    | Extra          |
+-----------------+---------------------+------+-----+------------+----------------+
| mur_userid      | int(10) unsigned    | NO   | MUL | NULL       |                |
| capitalsourceid | int(10) unsigned    | NO   | PRI | NULL       | auto_increment |
| type            | enum('1','2')       | NO   |     | 1          |                |
| state           | enum('1','2')       | NO   |     | 1          |                |
| accountnumber   | bigint(20)          | YES  |     | NULL       |                |
| bankcode        | bigint(20)          | YES  |     | NULL       |                |
| comment         | varchar(255)        | YES  |     | NULL       |                |
| validtil        | date                | NO   |     | 2999-12-31 |                |
| validfrom       | date                | NO   |     | 1970-01-01 |                |
| att_group_use   | tinyint(1) unsigned | NO   |     | 0          |                |
+-----------------+---------------------+------+-----+------------+----------------+

mysql> desc users;
+------------+---------------------+------+-----+---------+----------------+
| Field      | Type                | Null | Key | Default | Extra          |
+------------+---------------------+------+-----+---------+----------------+
| userid     | int(10) unsigned    | NO   | PRI | NULL    | auto_increment |
| name       | varchar(20)         | NO   | UNI | NULL    |                |
| password   | varchar(40)         | NO   |     | NULL    |                |
| att_new    | tinyint(1) unsigned | NO   |     | NULL    |                |
| perm_login | tinyint(1) unsigned | NO   |     | NULL    |                |
| perm_admin | tinyint(1) unsigned | NO   |     | NULL    |                |
+------------+---------------------+------+-----+---------+----------------+

mysql> desc groups;
+---------+------------------+------+-----+---------+----------------+
| Field   | Type             | Null | Key | Default | Extra          |
+---------+------------------+------+-----+---------+----------------+
| groupid | int(10) unsigned | NO   | PRI | NULL    | auto_increment |
| name    | varchar(20)      | NO   | UNI | NULL    |                |
+---------+------------------+------+-----+---------+----------------+

mysql> desc user_groups;
+-------------+------------------+------+-----+---------+-------+
| Field       | Type             | Null | Key | Default | Extra |
+-------------+------------------+------+-----+---------+-------+
| mur_userid  | int(10) unsigned | NO   | PRI | NULL    |       |
| mgr_groupid | int(10) unsigned | NO   | PRI | NULL    |       |
| validfrom   | date             | NO   |     | NULL    |       |
| validtil    | date             | NO   |     | NULL    |       |
+-------------+------------------+------+-----+---------+-------+

Die Entity-Definition von "capitalsources":

Java:
@Entity(name = "capitalsources")
public class CapitalSource implements Serializable {

	private static final long	serialVersionUID	= -8858990315543934325L;

	@ManyToOne()
	@JoinColumn(name = "mur_userid", referencedColumnName = "userid")
	private User				user;

	@Id
	private long				capitalsourceid;
	private int					type;
	private int					state;
	private long				accountnumber;
	private long				bankcode;
	private String				comment;
	private Date				validtil;
	private Date				validfrom;
	@Column(name = "att_group_use", columnDefinition = "TINYINT(1)")
	private boolean				attGroupUse;

Nun folgendes Problem:

Eine capitalsource gehört immer einem User (capitalsources.mur_userid). Mittels capitalsources.att_group_use kann er aber angeben, das diese capitalsource auch von Usern genutzt werden kann, welche auch in seiner Gruppe sind. Die "Nutzung" umschließt aber ausschliesslich lesende Nutzung.

Wenn es darum geht, eine einzelne capitalsource zu finden, eine neue anzulegen, oder eine bestehende zu veraendern, ist der Zugriff auf die "capitalsources" Entitaet richtig.
Wenn es aber darum geht, alle capitalsources-Einträge zu ermitteln, welche man lesen/verwenden darf, soll dies ueber die View geschehen.
Bisher (ohne Hibernate) hatte ich dafür folgende View:

Code:
CREATE OR REPLACE SQL SECURITY INVOKER VIEW vw_capitalsources (
   mur_userid
  ,mug_mur_userid
  ,capitalsourceid
  ,type
  ,state
  ,accountnumber
  ,bankcode
  ,comment
  ,validtil
  ,validfrom
  ,att_group_use
  ) AS
      SELECT mcs.mur_userid
            ,mug.mug2_mur_userid
            ,mcs.capitalsourceid
            ,mcs.type
            ,mcs.state
            ,mcs.accountnumber
            ,mcs.bankcode
            ,mcs.comment
            ,(CASE
                WHEN mug.mug2_mur_userid != mcs.mur_userid AND mcs.validtil  > mug.validtil THEN mug.validtil
                ELSE mcs.validtil
              END)
            ,(CASE
                WHEN mug.mug2_mur_userid != mcs.mur_userid AND mcs.validfrom < mug.validfrom THEN mug.validfrom
                ELSE mcs.validfrom
              END)
            ,mcs.att_group_use
        FROM capitalsources     mcs
            ,vw_user_groups     mug
       WHERE (     mug.mug1_mur_userid = mcs.mur_userid
               AND mcs.validfrom < mug.validtil
               AND mcs.validtil  > mug.validfrom
             )
          OR (     mug.mug1_mur_userid = mcs.mur_userid
               AND mug.mug2_mur_userid = mcs.mur_userid
             );

Die Abfrage für userid "4" erfolgt wie folgt:

Code:
select * from vw_capitalsources where mug_mur_userid=4 and (mur_userid=4 or att_group_use=1);

Wie kann ich die View verwenden beim Auslesen der capitalsources? Aktuell verwende ich zu Testzwecken noch:

Java:
	public List<CapitalSource> getAllValidCapitalSources(User user, Date date) {
		String q = "SELECT c from "
				+ CapitalSource.class.getName()
				+ " c WHERE c.user = :user AND :date BETWEEN c.validfrom AND c.validtil";
		Query query = entityManager.createQuery(q);
		query.setParameter("user", user);
		query.setParameter("date", date);
		@SuppressWarnings("unchecked")
		List<CapitalSource> capitalSources = query.getResultList();
		return capitalSources;
		
	}

Aber das greift halt nur meine ab, und nicht die meiner Gruppenmitglieder.
 
S

SlaterB

Gast
macht vw_user_groups noch was wichtiges?
ich sehe im Moment nicht so ganz die Komplexität, warum kann man

Code:
select * from vw_capitalsources where mug_mur_userid=4 and (mur_userid=4 or att_group_use=1);
nicht direkt auf die Tabelle capitalsources aufrufen, notfalls noch mit Join auf den zugehörigen User/ Gruppe?

Code:
select * from capitalsources c, user_groups ucg where c.mur_userid= ucg.userId 
and ( (ucg.mgr_groupid = [die bekannte GruppenId] + Gruppenzugriff) or eigene UserId)
and sonstiges wie ValidFrom
in HQL kann man gut nur die capitalsources selektieren, Rest hängt bisschen davon ab wie der Rest modelliert ist,
notfalls genauso Schritt um Schritt
 

OlliL

Bekanntes Mitglied
Du meinst die komplette View in HQL "umschreiben" und dann auf diese verzichten? Die View ist halt schon da, da sie von einer anderen Applikation aus bereits verwendet wird. Wollte mir die "Duplizierung" des SQLs sparen. Gibt es keinen anderen Weg? Im Sinne von "mache mir eine entity 'capitalsources', aber verwende eine andere Datenquelle".
 
S

SlaterB

Gast
wie gesagt sehe ich nicht unbedingt überhaupt eine Not für eine View,
kann man nicht wie von mir geschrieben eine Query auf die Tabellen absetzen?
 

OlliL

Bekanntes Mitglied
Klar - geht sicherlich irgendwie. Man muss halt die komplette Logik der View nachbilden. So Fälle wie, eine Capitalsource ist länger gültig, als die Gruppenzugehörigkeit - dann wird in der View die Gültigkeit der Capitalsource "nach aussen hin" beschränkt auf die Gültigkeit der Gruppenzuordnung.

Oder... ein User kann Mitglied in mehreren Gruppen sein. Um das abzubilden bedarf es halt nen Self-Join auf user_groups (in der View aufgelöst via vw_user_groups).

Also ein "einfacher" Query ist es eben nicht - daher gibt es zur Abstrahierung gegenüber den Clients diese View welche ich gerne verwenden würde ohne sie nachzubilden in HQL. Wenn sich irgendwas an der Logik ändert, würde ich zukünftig gerne nur die View ändern müssen und nicht 2 Stellen....

Daher die Frage ob es geht die View zu verwenden.
 
Zuletzt bearbeitet:

OlliL

Bekanntes Mitglied
Hallo SlaterB,

ok, das beruhigt mich :)
Hast du einen Link wo ich mich dazu belesen könnte bzw. einen Hinweis wie ich das gestallten könnte mal über die Tabelle und mal über die View zu gehen für diese Entität?
 

OlliL

Bekanntes Mitglied
Hallo Slater,

dann könnte ich aber nur noch lesend auf diese Entität zugreifen, oder? Ich will ja recht viele Aktionen auf "capitalsources" direkt machen. Nur bestimmte SELECTs wie "gib mir alle capitalsources welche ich verwenden darf" sollen auf diese View gehen.

Sollte ich eine 2. Entität machen welche die 1. erweitert und die 2. dann mit @Entity(name = "vw_capitalsources") annotieren? Ein einfacher Cast von der 2. auf die 1. Entität geht dann sicherlich nicht, da die 2. ein Attribut (mug_mur_userid) mehr hat als die 1.

Das würde ja irgendwie in etwa dem entsprechen was als 3. Lösungsvorschlag in deinem Link skiziert wurde, oder?

3. Do no make this a persistent entity. You can have standalone class representing the results and use can ask Hibernate to type case the result into that class using the "select new Metrics(.....) from.." syntax of HQL. This way the Metrics Java class is not required to be Persistent and will not be managed by Hibernate.

Oder habe ich dich falsch verstanden?
 
S

SlaterB

Gast
ich selber habe den Link nur gerade nach naheliegenden Stichwörtern gesucht und gefunden,
nicht wirklich gelesen,
ich glaube 'nicht in Joins verwenden' habe ich dabei noch aufgeschnappt, genau das wäre jetzt aber mein nächster Vorschlag,
mache ich selber mit einer View auch:

Java:
        String q = "SELECT c from CapitalSource c, CapitalSourceView cv " 
                + "WHERE c.id = cv.id and ... Bedingungen an die View";
        Query query = entityManager.createQuery(q);
        query.setParameter("user", user);
        query.setParameter("date", date);
        @SuppressWarnings("unchecked")
        List<CapitalSource> capitalSources = query.getResultList();
ansonsten auch nur die Ids sammeln und zweite Query stellen

sorry falls alles zu altbacken von meinen Tipps,
die eleganten Fertig-Lösungen habe ich nicht gerade, so scheint es mir ;)
 
Zuletzt bearbeitet von einem Moderator:

OlliL

Bekanntes Mitglied
Hallo,

ich habe jetzt mein bestehendes CapitalSources-Objekt nach CapitalSourcesVw kopiert und noch erweitert um:

Java:
	@ManyToOne()
	@JoinColumn(name = "mug_mur_userid", referencedColumnName = "userid")
	private User user2;

Das und die Entity-Annotation ist auch die einzige Änderung gegenüber dem CapitalSources-Objekt.

Mein Hibernate-Zugriff sieht so aus:

Java:
	public List<CapitalSource> getAllValidCapitalSources(User user, Date date) {
		String q = "SELECT new "
				+ CapitalSource.class.getName()
				+ " (c.user,c.capitalsourceid,c.type,c.state,c.accountnumber,c.bankcode,c.comment,c.validtil,c.validfrom,c.attGroupUse) from "
				+ CapitalSourceVw.class.getName()
				+ " c WHERE c.user2 = :user AND (c.user = :user OR c.attGroupUse = 1) AND :date BETWEEN c.validfrom AND c.validtil";
		Query query = entityManager.createQuery(q);
		query.setParameter("user", user);
		query.setParameter("date", date);
		@SuppressWarnings("unchecked")
		List<CapitalSource> capitalSources = query.getResultList();
		return capitalSources;
	}

Das funktioniert auch soweit... ich würde nun natürlich gerne vermeiden, CapitalSource zu duplizieren. Ich hatte mal folgendes versucht:

Java:
@Entity(name = "vw_capitalsources")
public class CapitalSourceVw extends CapitalSource implements Serializable{

	private static final long serialVersionUID = -6785828788124357172L;
	
	@ManyToOne()
	@JoinColumn(name = "mug_mur_userid", referencedColumnName = "userid")
	private User user2;
}

Damit bekomme ich beim Zugriff via Hibernate aber folgende Exception:

Code:
Caused by: com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Unknown column 'capitalsou0_.DTYPE' in 'where clause'

Was will er mir sagen? ;)
 
S

SlaterB

Gast
das hat glaube ich irgendwas mit 'Discriminator column' zu tun bei Vererbung der Mappings
java - Single Table Inheritance WITHOUT Discriminator column - Stack Overflow

ich wollte jetzt vorgeschlagen, drei Klassen zuschreiben, eine CapitalSourceBase mit allen Attributen, zwei kleine erbende Klassen,
aber das kommt ja mit den Annotations nicht gut hin, schon Pech wenn das in den Klassen selber stehen muss,
würde ich (nach Möglichkeit) nie verwenden, es gibt ja auch externe hbm.xml-Mappings

vielleicht reicht es, wenn du in der Basisklasse alles außer '@Entity(name = "capitalsources")' schreibst und dann zwei Subklassen darauf
 

OlliL

Bekanntes Mitglied
Jo,

habe ich mal probiert.

eine
@MappedSuperclass
CapitalSourcesRoot implements Serializable {

eine
@Entity(name = "capitalsources")
public class CapitalSource extends CapitalSourceRoot implements Serializable {

eine
@Entity(name = "vw_capitalsources")
public class CapitalSourceVw extends CapitalSourceRoot implements Serializable{


Der Query scheint nun zu klappen.
Wenn jetzt aber mein Client via EJB das CapitalSource Objekt zurück bekommt, gibts:

Code:
Caused by: java.io.InvalidClassException: my.project.entity.CapitalSource; Class does not extend stream superclass
	at org.jboss.marshalling.river.RiverUnmarshaller.doReadClassDescriptor(RiverUnmarshaller.java:909)

im Client...
ein wenig zum Haare raufen ;)
 
S

SlaterB

Gast
eine erstaunlich seltende Fehlermeldung nach Suchmaschinen,
gemappte Annotation-verseuchte Klassen bieten sich aber eh nicht für RMI an, die andere Seite soll doch nichts von Hibernate wissen müssen,
im Extremfall hast du nichtmal Objekte deiner Klasse sondern Proxys..,
besonders wenn noch Listen oder sonstige Datenstrukturen hineinkommen, mit LazyLoading noch nicht initialisiert...

da lieber Transfer-Objekte, nun wirklich Quellcode mindesten einmal kopieren..


wenn generell normale Objekte in Vererbung in RMI nicht gehen, ist das ein Thema für sich,
da fällt mir nix mehr ein, natürlich hoffentlich alle Serializable
 

OlliL

Bekanntes Mitglied
Generell klappt es ja, nur in dem hier speziellen Fall nicht.
Transfer-Objekte habe ich aber nur für den Weg Client -> EJB da ich den moeglichen Clients nicht "traue" ;)
Für den Weg EJB -> Clients dachte ich, kann ich ruhig die Entities rausgeben, zumal die Transfer-Objekte auch nur primitive Datentypen beinhalten (userid anstatt User-Objekt, String anstatt Date usw).

Naja, ich muss mir wohl nochmal ein paar Gedanken machen - so ganz gluecklich bin ich noch nicht mit den moeglichen Loesungen ;)
 

Ähnliche Java Themen


Oben