JavaFX:基于属性的实体与基于属性的包装器

JavaFX:基于属性的实体与基于属性的包装器,java,jpa,javafx,properties,wrapper,Java,Jpa,Javafx,Properties,Wrapper,我使用的是包含JavaFX属性的模型实体,它允许我更改单个位置上的值,将它们绑定到UI,并在模型实体数组中添加具有额外条件的更改侦听器(唯一值等) 我必须将模型存储在数据库中,因此问题如下: 我是否应该将模型实体转换为JPA实体(使用AccessType.PROPERTY设置值),而不需要将所有值(或所有时间)都设置为属性,或者使用基于属性的包装器(面向)创建经典的基于值的实体类来访问它 注意:一些可绑定属性根本不需要持久化。您是否应该使用特定的技术是基于意见的,因此我不会在这里回答您的确切问题

我使用的是包含JavaFX属性的模型实体,它允许我更改单个位置上的值,将它们绑定到UI,并在模型实体数组中添加具有额外条件的更改侦听器(唯一值等)

我必须将模型存储在数据库中,因此问题如下:

我是否应该将模型实体转换为JPA实体(使用AccessType.PROPERTY设置值),而不需要将所有值(或所有时间)都设置为属性,或者使用基于属性的包装器(面向)创建经典的基于值的实体类来访问它


注意:一些可绑定属性根本不需要持久化。

您是否应该使用特定的技术是基于意见的,因此我不会在这里回答您的确切问题。我将只提供一些正反两方面的选择

直接在JPA注释实体类中使用JavaFX属性的优点是,可以保持设计的简单性,每个实体只有一个类,而不是实体注释类周围的包装类

直接在JPA实体类中使用JavaFX属性的缺点是:

  • 潜在的性能成本。JavaFX属性比它们所表示的普通数据类型“更重”,并且在创建它们以及为其侦听器创建容器等方面会有一些成本。这可以以一些冗长的代价最小化(或者可能消除),见下文
  • 添加对JavaFXAPI的依赖关系。虽然JavaFX附带Oracle的标准JDK,但它不是JSE规范的必需部分,某些实现(例如OpenJDK)也不包含它。这在实践中可能不是什么大问题,但您应该意识到这一点
  • 通过使用“超级惰性”模式,可以最大限度地降低使用JavFX属性的成本。此处,仅当实际使用属性对象时(例如,如果您向它们注册了侦听器),才会创建属性对象;否则,将使用具有相同数据类型的代理项字段:

    @Entity
    @Access(AccessType.PROPERTY)
    public class Person {
    
        private IntegerProperty age ;
        private int _age ;
        private StringProperty name ;
        private String _name ;
    
        private int id ;
    
        @Id
        public int getId() {
            return id ;
        }
    
        public void setId(int id) {
            this.id = id ;
        }
    
        public IntegerProperty ageProperty() {
            if (age == null) {
                age = new SimpleIntegerProperty(_age);
            }
            return age ;
        }
    
        public int getAge() {
            if (age == null) {
                return _age ;
            } else {
                return age.get();
            }
        }
    
        public void setAge(int age) {
            if (this.age == null) {
                _age = age ;
            } else {
                this.age.set(age);
            }
        }
    
        public StringProperty nameProperty() {
            if (name == null) {
                name = new SimpleStringProperty(_name);
            } 
            return name ;
        }
    
        public String getName() {
            if (name == null) {
                return _name ;
            } else {
                return name.get();
            }
        }
    
        public void setName(String name) {
            if (this.name == null) {
                _name = name ;
            } else {
                this.name.set(name);
            }
        }
    }
    
    这基本上避免了(几乎)由于在非JavaFX环境中使用此类而产生的任何性能开销,因为除非通过
    xxxProperty()
    方法显式请求,否则不会实例化属性。请注意,调用这些方法是注册侦听器的唯一方法,因此如果已注册侦听器,则代码保证在随后调用
    setXxx(…)
    时通知这些侦听器。这里的代价是一些代码冗长和一些冗余空检查的非常小的代价(JVM可能会为此进行优化)

    这种技术显然无法解决对javafxapi的依赖性问题

    另一个可能的选择是将普通JavaBean与属性更改侦听器一起使用:

    @Entity
    public class Person {
        @Id
        private int id ;
    
        private int age ;
        private String name ;
    
        private final PropertyChangeSupport pcs = new PropertyChangeSupport(this);
    
        public int getAge() {
            return age ;
        }
    
        public void setAge(int age) {
            int oldAge = age ;
            this.age = age ;
            pcs.firePropertyChange("age", oldAge, age);
        }
    
        public String getName() {
            return name ;
        }
    
        public void setName(String name) {
            String oldName = name ;
            this.name = name ;
            pcs.firePropertyChange("name", oldName, name);
        }
    
        // ...
    }
    
    现在,在JavaFX客户端中,您可以执行以下操作:

    TableView<Person> contactTable = new TableView<>();
    TableColumn<Person, String> nameCol = new TableView<>("Name");
    nameCol.setCellValueFactory(cellData -> {
        try {
            return JavaBeanStringPropertyBuilder.create()
                .bean(cellData.getValue())
                .name("name")
                .build();
        } catch (Exception exc) {
            return new RuntimeException(exc);
        }
    });
    
    具有明显的实现

    @Entity
    public class PersonEntity implements Person {
        private int age ;
        private String name ;
        @Id
        private int id ;
    
        // get/set methods omitted...
    }
    


    现在,在JavaFX客户机中,您可以拥有一个JSON引擎,该引擎将JSON从
    PersonFX
    实例中[反]序列化,在服务器上,您可以拥有一个JSON引擎,该引擎将相同的JSON数据从
    PersonEntity
    实例中[反]序列化。由于JSON引擎将只通过调用get/set方法来工作,因此从其角度来看,对象本质上具有相同的形式。自从我开始使用JavaFX以来,我还没有处理过与XML数据之间的序列化,因此我不确定同样的方法是否适用于XML数据,但我认为您也可以做到这一点。通过在实现类中定义
    readObject
    writeObject
    方法,您甚至可以使用Java序列化流实现这一点,这些方法需要相同形式的数据(或使用序列化代理)。

    您能澄清您的问题吗?您正在询问使用JavaFX属性实现JPA实体是否是一个好的设计,当该实体有时将在JavaFX上下文中使用(需要可观察的属性),有时在不同的上下文中使用(不需要属性中的任何功能)事实上,这正是我要问的。我越想它,我总是回到某种包装上(顺便说一句,谢谢:-)。应用程序不需要处理来自多个源的数据(序列化)。实体将主要代表统计结果(在大多数情况下)。但是,我必须创建一个手动工具,用于测量和插入这些数据。这可能会改变特性,可能会创建新的(更自动化的)工具,或者引入不同的测量方法。包装器可以帮助我独立地调整每个案例的外观。
    @Entity
    public class PersonEntity implements Person {
        private int age ;
        private String name ;
        @Id
        private int id ;
    
        // get/set methods omitted...
    }
    
    public class PersonFX implements Person {
        private final StringProperty name = new SimpleStringProperty() ;
        private final IntegerProperty age = new SimpleIntegerProperty() ;
    
        public StringProperty nameProperty() {
            return name ;
        }
    
        @Override
        public final String getName() {
            return nameProperty().get();
        }
    
        @Override
        public final void setName(String name) {
            nameProperty().set(name);
        }
    
        // similarly for age...
    }