java – how to create JTables with POJOs with a composite key with a single table model

I’m trying to implement the use of reflection and annotations, just like the solution suggested by ardayigit on how to create a JTable with POJOs with a single table model, but the problem comes with the first field of Person, which is a two-field composite key of type PersonId.

Here is the extract for creating the JTable:

//List<Person> persons
ModelDAO model = new ModelDAO(persons);
        JTable table = new JTable(model) {
            @Override
            public Class getColumnClass(int column) {
                try {
                    return getValueAt(0, column).getClass();
                } catch (NullPointerException e) {
                    return String.class;
                }
            }
        };

here is the code of the POJO Person:

@Entity
@Table(name = "Person",
         schema = "dbo",
         catalog = "DB"
)
public class Person implements Serializable {

    private PersonId id;

    @ColumnAttribute(columnName = "t_name", order = 2, updatable = true)
    private String tName;

    @ColumnAttribute(columnName = "t_address", order = 4, updatable = true)
    private String tAddress;

    @ColumnAttribute(columnName = "t_single", order = 5, updatable = true)
    private boolean tSingle;

    public Person(PersonId id, String tName, String tAddress, boolean tSingle) {
        this.id = id;
        this.tName = tName;
        this.tAddress = tAddress;
        this.tSingle = tSingle;
    }

    @EmbeddedId

    @AttributeOverrides({
        @AttributeOverride(name = "tKey", column = @Column(name = "t_key", nullable = false, length = 8)),
        @AttributeOverride(name = "tReg", column = @Column(name = "t_reg", nullable = false, length = 2))
    })
    public PersonId getId() {
        return id;
    }

    public void setId(PersonId id) {
        this.id = id;
    }

    @Column(name = "t_name", length = 100)
    public String getTName() {
        return tName;
    }

    public void setTName(String tName) {
        this.tName = tName;
    }

    @Column(name = "t_address", length = 60)
    public String getTAddress() {
        return tAddress;
    }

    public void setTAddress(String tAddress) {
        this.tAddress = tAddress;
    }

    @Column(name = "t_single", nullable = false, columnDefinition = "BIT")
    public boolean getTSingle() {
        return tSingle;
    }

    public void setTSingle(boolean tSingle) {
        this.tSingle = tSingle;
    }
}

here is PersonId:

@Embeddable
public class PersonId implements Serializable {
    @ColumnAttribute(columnName = "t_key", order = 1, updatable = false)
    private String tKey;
    
    @ColumnAttribute(columnName = "t_reg", order = 3, updatable = false)
    private String tReg;

    public PersonId() {
    }

    public PersonId(String tKey, String tReg) {
        this.tKey = tKey;
        this.tReg = tReg;
    }

    @Column(name="t_key", nullable=false, length=8)
    public String getTKey() {
        return tKey;
    }

    public void setTKey(String tKey) {
        this.tKey = tKey;
    }

    @Column(name="t_reg", nullable=false, length=2)
    public String getTReg() {
        return tReg;
    }

    public void setTReg(String tReg) {
        this.tReg = tReg;
    }

    @Override
    public int hashCode() {
        int hash = 7;
        hash = 79 * hash + Objects.hashCode(this.tKey);
        hash = 79 * hash + Objects.hashCode(this.tReg);
        return hash;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj == null) {
            return false;
        }
        if (getClass() != obj.getClass()) {
            return false;
        }
        final PersonId other = (PersonId) obj;
        if (!Objects.equals(this.tKey, other.tKey)) {
            return false;
        }
        if (!Objects.equals(this.tReg, other.tReg)) {
            return false;
        }
        return true;
    }
}

As you can see, I´ve modified a little the proposal of ardaigit and created ColumnAttributes and ColumnAttribute to get it work the way it does AttributeOverrides and AttributeOverridebut not sure if you got it right.

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface ColumnAttributes {
    public ColumnAttribute[] value();
}
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface ColumnAttribute {
    String columnName();
    int order();
    boolean updatable(); 
}

At this point, the method getColumnCount of ModelDAO returns 4, because tName and tReg are counted as 1 –> id:

public class ModelDAO extends AbstractTableModel {

    private final List<?> data; 

    public ModelDAO(List data) {
        this.data = data;
    }

    @Override
    public int getRowCount() {
        return this.data.size(); 
    }

    @Override
    public int getColumnCount() {
        return data.get(0).getClass().getDeclaredFields().length; 
    }

    @Override
    public Object getValueAt(int rowIndex, int columnIndex) {
        try {
            Method[] methods = data.get(rowIndex).getClass().getDeclaredMethods(); 
            Arrays.sort(methods, new ComparatorReflection<>()); 
            return methods[columnIndex].invoke(data.get(rowIndex), (Object[]) null);
        } catch (IllegalAccessException | IllegalArgumentException | SecurityException | InvocationTargetException ex) {
            ex.printStackTrace(); 
        }
        throw new UnsupportedOperationException("Not supported yet."); 
    }
    

    @Override
    public Class<?> getColumnClass(int columnIndex) {
        Field[] fields = data.get(0).getClass().getDeclaredFields(); 
        Arrays.sort(fields, new ComparatorReflection<>()); 
        return fields[columnIndex].getType(); 
    }

    @Override
    public String getColumnName(int column) {
        Field[] fields = data.get(0).getClass().getDeclaredFields(); 
        Arrays.sort(fields, new ComparatorReflection<>()); 
        return fields[column].getAnnotation(ColumnAttribute.class).columnName(); 
    }

    @Override
    public boolean isCellEditable(int rowIndex, int columnIndex) {
        Field[] fields = data.get(rowIndex).getClass().getDeclaredFields(); 
        Arrays.sort(fields, new ComparatorReflection<>()); 
        return fields[columnIndex].getAnnotation(ColumnAttribute.class).updatable(); 
    }
}

Is there a way to “extract” the fields of PersonId in order to get the right column counts, values, classes and names to show them in an editable JTable?

Leave a Comment