У меня есть проект гобелена 5, который содержит следующее:
Абстрактная сущность в пакете сущностей, которая наследуется всеми другими конкретными сущностями.
import java.io.Serializable; import javax.persistence.Basic; import javax.persistence.Column; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.MappedSuperclass; @MappedSuperclass public class AbstractEntity implements Serializable, Comparable<AbstractEntity> { private static final long serialVersionUID = 1L; @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Basic(optional = false) @Column(name = "ID") protected Integer id; @Override public int compareTo(AbstractEntity o) { return this.toString().compareTo(o.toString()); } }
Несколько конкретных сущностей (большую часть их тел я опущу, так как считаю, что это совершенно не имеет отношения к вопросу, это простые классы данных сущностей), которые наследуют AbstractEntity. Пример одного из таких классов сущностей:
//Imports go here @Entity @Table(name = "room") @NamedQueries({ @NamedQuery(name = "Room.findAll", query = "SELECT r FROM Room r")}) public class Room extends AbstractEntity { private static final long serialVersionUID = 1L; @Basic(optional = false) @Column(name = "ROOM_TYPE") @Validate("required") @Enumerated(EnumType.STRING) private RoomType roomType; //rest of the attributes and their annotations go here, as well as setter/getter methods
Общий интерфейс DAO
import com.mycompany.myproject.entities.AbstractEntity; import java.util.List; public interface GenericDAO <T extends AbstractEntity>{ public abstract List<T> getListOfObjects(Class myclass); public abstract T getObjectById(Integer id, Class myclass); public abstract T addOrUpdate(T obj); public abstract T delete(Integer id, Class myclass); }
Реализация универсального интерфейса DAO, который привязан к нему в пакете AppModule in services с помощью binder.bind.
import com.mycompany.myproject.entities.AbstractEntity; import java.util.Collections; import java.util.List; import org.hibernate.Criteria; import org.hibernate.Session; import org.hibernate.criterion.Restrictions; public class GenericDAOImpl<T extends AbstractEntity> implements GenericDAO<T> { private Session session; @Override public List getListOfObjects(Class myclass) { List<T> list = session.createCriteria(myclass).setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY).list(); Collections.sort(list); return list; } @Override public T getObjectById(Integer id, Class myclass) { AbstractEntity ae = (AbstractEntity) session.createCriteria(myclass) .add(Restrictions.eq("id", id)).list().get(0); return (T) ae; } @Override public AbstractEntity addOrUpdate(AbstractEntity obj) { return (T) session.merge(obj); } @Override public T delete(Integer id, Class myclass) { AbstractEntity ae = (AbstractEntity) session.createCriteria(myclass) .add(Restrictions.eq("id", id)).list().get(0); session.delete((T) ae); session.flush(); return (T) ae; } }
Общий класс Java редактора в пакете компонентов
import com.mycompany.myproject.entities.AbstractEntity; import com.mycompany.myproject.services.GenericDAO; import java.util.List; import org.apache.tapestry5.ComponentResources; import org.apache.tapestry5.PropertyConduit; import org.apache.tapestry5.annotations.Persist; import org.apache.tapestry5.annotations.Property; import org.apache.tapestry5.beaneditor.BeanModel; import org.apache.tapestry5.hibernate.annotations.CommitAfter; import org.apache.tapestry5.ioc.annotations.Inject; import org.apache.tapestry5.services.BeanModelSource; import org.apache.tapestry5.services.PropertyConduitSource; public class GenericEditor<T extends AbstractEntity> { @Inject private PropertyConduitSource conduit; @Inject private GenericDAO genericDAO; @Property @Persist private T bean; @Property private T row; @Inject private BeanModelSource bms; @Inject private ComponentResources cr; private Class myclass; { PropertyConduit conduit1 = conduit.create(getClass(), "bean"); myclass = conduit1.getPropertyType(); } public List<T> getGrid(){ List<T> temp = genericDAO.getListOfObjects(myclass); return temp; } public BeanModel<T> getFormModel(){ return bms.createEditModel(myclass, cr.getMessages()).exclude("id"); } public BeanModel<T> getGridModel(){ return bms.createDisplayModel(myclass, cr.getMessages()).exclude("id"); } @CommitAfter Object onActionFromDelete(int id){ genericDAO.delete(id, myclass); return this; } @CommitAfter Object onActionFromEdit(int row){ bean = (T)genericDAO.getObjectById(row, myclass); return this; } @CommitAfter Object onSuccess(){ genericDAO.addOrUpdate(bean); try { bean = (T) myclass.newInstance(); } catch(Exception ex){ } return this; }
Связанный файл .tml для класса Java GenericEditor
<!--GenericEditor.tml--> <html xmlns:t="http://tapestry.apache.org/schema/tapestry_5_3.xsd" xmlns:p="tapestry:parameter"> <t:beaneditform object="bean" t:model="formModel" > </t:beaneditform> <t:grid t:source="grid" t:model="gridModel" add="edit,delete" row="row"> <p:editCell> <t:actionlink t:id="edit" context="row">Edit</t:actionlink> </p:editCell> <p:deleteCell> <t:actionlink t:id="delete" context="row">Delete</t:actionlink> </p:deleteCell> </t:grid> </html>
Кроме того, в пакете pages есть несколько классов Java, а также связанные с ними файлы .tml, которые изначально были созданы без использования genericDAO, но с использованием конкретных DAO, поэтому они выглядели так (пример одного из них):
import com.mycompany.myproject.entities.Room; import com.mycompany.myproject.services.RoomDAO; import java.util.ArrayList; import java.util.List; import org.apache.tapestry5.annotations.Persist; import org.apache.tapestry5.annotations.Property; import org.apache.tapestry5.hibernate.annotations.CommitAfter; import org.apache.tapestry5.ioc.annotations.Inject; public class RoomPage { @Property private Room room; @Property private Room roomrow; @Inject private RoomDAO roomDAO; @Property private List<Room> rooms; void onActivate(){ if(rooms==null){ rooms = new ArrayList<Room>(); } rooms = roomDAO.getListOfRooms(); } @CommitAfter Object onSuccess(){ roomDAO.addOrUpdateRoom(room); room = new Room(); return this; } @CommitAfter Object onActionFromEdit(Room room2){ room = room2; return this; } @CommitAfter Object onActionFromDelete(int id){ roomDAO.deleteRoom(id); return this; } }
И связанный файл .tml:
<!--RoomPage.tml--> <html t:type="layout" title="RoomPage" xmlns:t="http://tapestry.apache.org/schema/tapestry_5_3.xsd" xmlns:p="tapestry:parameter"> <div class="row"> <div class="col-sm-4 col-md-4 col-lg-3"> <t:beaneditform object="room" exclude="id" reorder="roomtype, floor, tv, internet" submitlabel="message:submit-label"/> </div> <div class="col-sm-8 col-md-8 col-lg-9"> <t:grid t:source="rooms" exclude="id" add="edit,delete" row="roomrow" include="roomtype, floor, tv, internet"> <p:editCell> <t:actionlink t:id="edit" context="roomrow">Edit</t:actionlink> </p:editCell> <p:deleteCell> <t:actionlink t:id="delete" context="roomrow.id">Delete</t:actionlink> </p:deleteCell> </t:grid> </div> </div> </html>
Приведенный выше код с использованием конкретного DAO работает корректно, на странице появляется форма для ввода новых строк в базу данных, а также сетка со строками из таблицы базы данных.
Итак, основная идея заключалась в том, чтобы использовать GenericEditor вместе с genericDAO, чтобы уменьшить объем необходимого кода и манипулировать любой из таблиц базы данных, используя BeanEditForm для ввода новых строк в таблицу и Grid для отображения всех строк из таблицы и удалить или отредактировать их. Теоретически это должно работать для любой сущности, наследующей класс AbstractEntity, поэтому нет необходимости создавать отдельную пару интерфейса/реализации DAO для каждой сущности.
Проблема в том, что я не могу заставить это работать так, как задумано, так как я не уверен, как на самом деле использовать GenericEditor, показанный выше. Я попытался сделать следующее:
RoomPage.java после изменений:
import com.mycompany.myproject.components.GenericEditor; import com.mycompany.myproject.entities.Room; public class RoomPage{ @Component private GenericEditor<Room> ge; }
RoomPage.tml после изменений:
<!--RoomPage.tml--> <html t:type="layout" title="RoomPage" xmlns:t="http://tapestry.apache.org/schema/tapestry_5_3.xsd" xmlns:p="tapestry:parameter"> <t:GenericEditor t:id="ge" /> </html>
Но это, по-видимому, не работает, так как все, что это дало, было исключением нулевого указателя, а также этой ошибкой:
Blockquote [ERROR] pages.RoomPage Ошибка очереди рендеринга в SetupRender[RoomPage:ge.grid]: Ошибка чтения параметра «источник» компонента RoomPage:ge.grid: org.apache.tapestry5.ioc.internal.util.TapestryException org.apache .tapestry5.ioc.internal.util.TapestryException: Ошибка чтения параметра «источник» компонента RoomPage:ge.grid: org.apache.tapestry5.ioc.internal.util.TapestryException [в пути к классам: com/mycompany/myproject/components/ GenericEditor.tml, строка 5]
Затем я попытался полностью удалить элемент сетки и запустить GenericEditor только с BeanEditForm. Это привело к фактической загрузке страницы, но вместо отображения ожидаемой формы на странице с полями объекта «Комната» и кнопкой «Создать/обновить» в конце формы появилась только кнопка «Создать/обновить», без какого-либо поля, как если бы BeanEditForm был создан на объекте без каких-либо атрибутов. Нажатие кнопки «Создать/обновить» создает еще одно исключение нулевого указателя.
В целях отладки я изменил GenericEditor.java, чтобы он работал неуниверсальным образом, создав в нем еще один атрибут универсального типа T, а затем инициализировав его как новый объект типа Room, приведенный как (T), и затем объявление класса атрибута того же типа, что и атрибут комнаты, как показано ниже.
private T room;
{
//PropertyConduit conduit1 = conduit.create(getClass(), "bean");
//class = conduit1.getPropertyType();
room = (T) new Room();
class = room.getClass();
}
Запустив приложение с этими изменениями (с отключенной сеткой и включенной только beaneditform), страница теперь правильно отображает все поля ввода. Это привело меня к выводу, что проблема кроется в том, что GenericEditor не получает через дженерик нужный тип, но я не знаю, верна ли моя логика, и даже если да, то как обойти эту проблему. Другим возможным источником проблемы может быть PropertyConduit, я не уверен, как именно он работает, и правильно ли я его использую, поэтому я не исключаю, что проблема возникает и там. В любом случае, мое основное предположение заключается в том, что я каким-то образом неправильно использую GenericEditor, поэтому, как следует из названия этого вопроса, как я должен использовать GenericEditor для правильного доступа к базе данных с ним?
Я искал в stackoverflow похожие проблемы, но мне не удалось найти ничего похожего ни здесь, ни где-либо еще. Я надеюсь, что кто-то здесь сможет помочь мне определить, в чем проблема, и помочь мне обойти ее, поскольку я действительно понятия не имею, как это сделать самостоятельно. Заранее спасибо.
Обновление: я провел дальнейшую отладку, пытаясь проверить, какой тип класса перенаправляется в myclass GenericEditor. Я изменил следующий бит GenericEditor.java:
{
PropertyConduit conduit1 = conduit.create(getClass(), "bean");
myclass = conduit1.getPropertyType();
}
к следующему:
{
PropertyConduit conduit1 = conduit.create(getClass(), "bean");
System.out.println("conduit1.toString(): "+conduit1.toString());
System.out.println("conduit1.getPropertyType().toString(): "+conduit1.getPropertyType().toString());
System.out.println("conduit1.getPropertyType().getName(): "+conduit1.getPropertyType().getName());
myclass = conduit1.getPropertyType();
System.out.println("myclass.getName(): "+myclass.getName());
}
и это привело к следующему результату:
conduit1.toString(): PropertyConduit[компонент com.mycompany.myproject.components.GenericEditor]
conduit1.getPropertyType().toString(): класс com.mycompany.myproject.entities.AbstractEntity
conduit1.getPropertyType().getName(): com.mycompany.myproject.entities.AbstractEntity
myclass.getName(): com.mycompany.myproject.entities.AbstractEntity
Что, я считаю, в значительной степени означает, что тип T, пересылаемый в GenericEditor, является AbstractEntity, а не Room, как предполагалось. Если мое предположение верно, я неправильно использую GenericEditor, поскольку я не получаю правильный класс, пересылаемый ему через дженерики, так как я должен перенаправить ему правильный класс? Или мое предположение неверно и здесь что-то еще не так?