Kategorien
Architektur Glassfish JPA

Ordered Lists in JPA – Do it yourself

Bei OneToMany und ManyToMany Relationen gibt es in JPA die Möglichkeit die Sortierreihenfolge festzulegen, mit der die Objekte aus der Datenbank kommen.

@OneToMany
@OrderBy("name ASC")
List<MyEntry> entries;

Dieses Konstrukt sorgt dafür, dass die MyEntry Objekte nach dem darin enthaltenen Feld „text“ in aufsteigender Reihenfolge aus der Datenbank kommen. Das funktioniert soweit auch ganz prima, bis man zu der Liste neue Elemente hinzufügt.

Ist eine Entity gelesen, hat der Programmierer die Verantwortung

Was bedeutet das? In dem Augenblick wo die Entity aus der Datenbank sortiert gelesen wurde, muss sich der Programmierer selber darum kümmern, dass eine neue Entity an der richtigen Stelle in der Liste eingefügt wird. Egal, wie die Cascade Einstellungen sind, egal, wie die Sortiereinstellungen sind. Wird eine Entity außerhalb der Sortierung (also beispielsweise an die letzte Stelle) an die Liste angefügt, bleibt sie da, solange das Objekt sich im internen Cache der JPA Implementation befindet. Das kann, wie versuche gezeigt haben, sehr lange sein. Da hilft kein find, kein close auf den Entity Manager und (zumindest bei Toplink mit Fetchtype.LAZY) auch kein refresh auf der Entity, die die Liste enthält.

Die einfachste Möglichkeit das Problem in den Griff zu kriegen ist, eine add Routine zu implementieren, die einen Comperator entsprechend der von JPA geforderten Sortierreihenfolge zur Verfügung stellt und das Objekt an der richtigen Stelle in die Liste einzufügen:

package jpatest.domain;

import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.NamedQuery;
import javax.persistence.OneToMany;
import javax.persistence.OrderBy;

@NamedQuery(name="MyOrderedTextCollection.findFirst",
    query="select p from MyOrderedTextCollection p")
/**
 *
 * @author tschuett
 */
@Entity
public class MyOrderedTextCollection {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;
    @OneToMany(mappedBy = "collection", cascade=CascadeType.ALL)
    @OrderBy("text ASC")
    private List<MyTextEntry> entries;

    public MyOrderedTextCollection() {
        entries = new ArrayList<MyTextEntry>();
    }

    public Long getId() {
        return id;
    }

    public List<MyTextEntry> getEntries() {
        return Collections.unmodifiableList(entries);
    }

    public void addEntry(String text) {
        if (text == null) {
            throw new NullPointerException(
                "text must not be null");
        }
        MyTextEntry entry = new MyTextEntry(this, text);
        int pos = Collections.binarySearch(entries, entry,
            new Comparator<MyTextEntry>() {
                public int compare(MyTextEntry entry0,
                  MyTextEntry entry1) {
                    return entry0.getText().compareTo(
                      entry1.getText());
            }
        });
        if (pos < 0)
        {
            pos = -(pos +1);
        }
        entries.add(pos, entry);
    }
}

Die Diskussion zu dem Thema lässt sich hier nachlesen:Diskussion in der Glassfish Mailingliste

Das vollständige Beispiel kann hier heruntergeladen werden: jpatest

Kategorien
Allgemeines JavaFX

JavaFX – Wusel Hack

JavaFX wird wohl irgendwann in der Zukunft mal die Art neu definieren, wie man mit der Java Familie GUIs baut. Das Previewrelease ist noch sehr pre Alpha. So gibt es derzeit kein Konzept für große Integer (long, BigInt) und die Anbindung an Java oder EJB Applikationen ist sehr rudimentär. Nichts desto trotz kann man damit wirklich angenehm leicht kleine Grafikprogrämmchen bauen. Da fühlt man sich gleich an die Zeit am alten Atari zurückversetzt.

Hier aus der Zeit dann auch ein kleines Programm, wo viele kleine schwarze Kugeln eine große rote Kugel jagen. Have Fun 😉

Mit WebStart ausführen

/*
 * Wusel.fx
 *
 * Created on 29.09.2008, 17:51:08
 */

package wusel;

import java.lang.Math;
import javafx.scene.*;
import javafx.scene.paint.*;
import javafx.scene.geometry.*;
import javafx.application.*;
import javafx.scene.transform.*;
import javafx.input.*;
import javafx.animation.*;
import java.lang.System;

/**
 * @author tschuett
 */

var WIDTH=800;
var HEIGHT=800;
var FOLLOWERS=200;

class Position {
    attribute x: Integer;
    attribute y: Integer;
}

class Target {
    attribute pos : Position;
}

class Follower {
    attribute target : Target;
    attribute direction: Number = Math.random() * Math.PI * 2;
    attribute pos: Position;
    attribute speed: Integer = 1;
    attribute maxSpeed: Integer =
    10 + (5 * Math.random()) as Integer;
    attribute turnSpeed =  Math.PI / 10;
    private attribute lockAngle = turnSpeed;
    private attribute lastDist: Number = 1E99;

    function move() {
        var dx = target.pos.x - pos.x;
        var dy = target.pos.y - pos.y;
        var dist = Math.sqrt(Math.pow(dx, 2) + Math.pow(dy,2));
        var angle =  Math.atan2(dy, dx) - direction ;
            if (
        speed < maxSpeed and lastDist > dist) {
                speed++;
        } else if( lastDist < dist and speed > 2)
        {
                speed--;
        }
        lastDist = dist;
            if (
        Math.abs(angle) < lockAngle) {
            direction += (
            Math.random() * turnSpeed * 2 - turnSpeed);
        } else if (angle < 0 or angle > Math.PI) {
            direction -= turnSpeed;
        } else {
            direction += turnSpeed;
        }
        direction = direction mod (Math.PI * 2.0);

        pos.x += (speed * Math.cos(direction)) as Integer;
        pos.y += (
        speed * Math.sin(direction)) as Integer;
    }
}

var t = Target {
    pos: Position {x:WIDTH / 2 ; y:HEIGHT / 2 }
}

var followers =
for(i in [0..FOLLOWERS]){
    Follower{
        target: t
        pos: Position {
            x:0;
            y:0;
        }
    }
}
Frame {
    visible: true
    width: WIDTH height: HEIGHT
    title: "Wusel"
    resizable: false
    closeAction: function (){
        System.exit(0);
    }
    stage: Stage {
        fill: Color.WHITE
        content: [
            Rectangle {
                x: 0, y: 0
                width: WIDTH, height: HEIGHT
                fill: Color.WHITE
                onMouseClicked:function(e:MouseEvent){
                    System.exit(0);
                }
                onMouseMoved: function (e:MouseEvent){
                    t.pos.x =
                    e.getStageX() as Integer;
                    t.pos.y =
                    e.getStageY() as Integer;
                }
            },
            for (f in followers){
                Circle {
                    centerX: bind f.pos.x , centerY: bind f.pos.y
                    radius: 2
                    fill: Color.BLACK
                }
            },
            Circle {
                centerX: bind t.pos.x , centerY: bind t.pos.y
                radius: 5
                fill: Color.RED
        }]
    }
}
var anim = Timeline { keyFrames: [
        KeyFrame { time: 20ms
            action: function(){
                for (f in followers){
                    f.move();
                }
            }

        }]
    repeatCount: Timeline.INDEFINITE
};
anim.start();