出于测试目的,我使用 NetBeans 助手开发了一个简单的 RESTful 应用程序。为此,我关注了这个视频:https ://www.youtube.com/watch?v=RNJynlcqHNs 。由于我使用的是最新版本的 NetBeans,因此该过程略有不同,但它应该具有相同的结果。在简历中,我使用了以下工具:
- NetBeans 8.1
- Glassfish 服务器 4.1.1
- MySQL(到目前为止没有任何问题)
助手们:
- 数据库中的实体类
- 来自实体类的 RESTful 服务
- RESTful Javascript 客户端(使用 Backbone+HTML5 构建“功能齐全”的客户端)
据我所知,该项目包括 EclipseLink (JPA 2.1) 和 Jersey。
项目部署正确,我可以直接从浏览器访问服务,但是当我尝试使用自动生成的 Javascript 客户端访问服务时,它并没有完全加载。另外,我在浏览器的控制台中收到此错误:
加载资源失败:服务器响应状态为 500(内部服务器错误)
我在 NetBeans/Glassfish“控制台”中收到此错误:
Advertencia: StandardWrapperValve[service.ApplicationConfig]: Servlet.service() for servlet service.ApplicationConfig threw exception
java.lang.NoClassDefFoundError: Could not initialize class org.eclipse.persistence.jaxb.BeanValidationHelper
at org.eclipse.persistence.jaxb.JAXBBeanValidator.isConstrainedObject(JAXBBeanValidator.java:257)
at org.eclipse.persistence.jaxb.JAXBBeanValidator.shouldValidate(JAXBBeanValidator.java:208)
etc...
这是 JPA 实体类代码:
package model;
import java.io.Serializable;
import java.util.List;
import javax.persistence.Basic;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlTransient;
/**
*
* @author USER
*/
@Entity
@Table(name = "oficina")
@XmlRootElement
@NamedQueries({
@NamedQuery(name = "Oficina.findAll", query = "SELECT o FROM Oficina o"),
@NamedQuery(name = "Oficina.findByIdOficina", query = "SELECT o FROM Oficina o WHERE o.idOficina = :idOficina"),
@NamedQuery(name = "Oficina.findByNumero", query = "SELECT o FROM Oficina o WHERE o.numero = :numero"),
@NamedQuery(name = "Oficina.findByDescripcion", query = "SELECT o FROM Oficina o WHERE o.descripcion = :descripcion")})
public class Oficina implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@Basic(optional = false)
@NotNull
@Column(name = "id_oficina")
private Integer idOficina;
@Basic(optional = false)
@NotNull
@Size(min = 1, max = 10)
@Column(name = "numero")
private String numero;
@Size(max = 100)
@Column(name = "descripcion")
private String descripcion;
@OneToMany(cascade = CascadeType.ALL, mappedBy = "idOficina")
private List<Investigador> investigadorList;
public Oficina() {
}
public Oficina(Integer idOficina) {
this.idOficina = idOficina;
}
public Oficina(Integer idOficina, String numero) {
this.idOficina = idOficina;
this.numero = numero;
}
public Integer getIdOficina() {
return idOficina;
}
public void setIdOficina(Integer idOficina) {
this.idOficina = idOficina;
}
public String getNumero() {
return numero;
}
public void setNumero(String numero) {
this.numero = numero;
}
public String getDescripcion() {
return descripcion;
}
public void setDescripcion(String descripcion) {
this.descripcion = descripcion;
}
@XmlTransient
public List<Investigador> getInvestigadorList() {
return investigadorList;
}
public void setInvestigadorList(List<Investigador> investigadorList) {
this.investigadorList = investigadorList;
}
@Override
public int hashCode() {
int hash = 0;
hash += (idOficina != null ? idOficina.hashCode() : 0);
return hash;
}
@Override
public boolean equals(Object object) {
// TODO: Warning - this method won't work in the case the id fields are not set
if (!(object instanceof Oficina)) {
return false;
}
Oficina other = (Oficina) object;
if ((this.idOficina == null && other.idOficina != null) || (this.idOficina != null && !this.idOficina.equals(other.idOficina))) {
return false;
}
return true;
}
@Override
public String toString() {
return "model.Oficina[ idOficina=" + idOficina + " ]";
}
}
这是服务代码:
package service;
import java.util.List;
import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import model.Oficina;
/**
*
* @author USER
*/
@Stateless
@Path("oficinas")
public class OficinaFacadeREST extends AbstractFacade<Oficina> {
@PersistenceContext(unitName = "grupoItosWSPU")
private EntityManager em;
public OficinaFacadeREST() {
super(Oficina.class);
}
@POST
@Override
@Consumes({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
public void create(Oficina entity) {
super.create(entity);
}
@PUT
@Path("{id}")
@Consumes({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
public void edit(@PathParam("id") Integer id, Oficina entity) {
super.edit(entity);
}
@DELETE
@Path("{id}")
public void remove(@PathParam("id") Integer id) {
super.remove(super.find(id));
}
@GET
@Path("{id}")
@Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
public Oficina find(@PathParam("id") Integer id) {
return super.find(id);
}
@GET
@Override
@Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
public List<Oficina> findAll() {
return super.findAll();
}
@GET
@Path("{from}/{to}")
@Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
public List<Oficina> findRange(@PathParam("from") Integer from, @PathParam("to") Integer to) {
return super.findRange(new int[]{from, to});
}
@GET
@Path("count")
@Produces(MediaType.TEXT_PLAIN)
public String countREST() {
return String.valueOf(super.count());
}
@Override
protected EntityManager getEntityManager() {
return em;
}
}
最后,这是 Javascript 代码:
var app = {
// Create this closure to contain the cached modules
module: function () {
// Internal module cache.
var modules = {};
// Create a new module reference scaffold or load an
// existing module.
return function (name) {
// If this module has already been created, return it.
if (modules[name]) {
return modules[name];
}
// Create a module and save it under this name
return modules[name] = {Views: {}};
};
}()
};
(function (models) {
// Model for Oficina entity
models.Oficina = Backbone.Model.extend({
urlRoot: "http://localhost:8080/grupoItosWS/api/oficinas/",
idAttribute: 'idOficina',
defaults: {
descripcion: "",
numero: ""
},
toViewJson: function () {
var result = this.toJSON(); // displayName property is used to render item in the list
result.displayName = this.get('idOficina');
return result;
},
isNew: function () {
// default isNew() method imlementation is
// based on the 'id' initialization which
// sometimes is required to be initialized.
// So isNew() is rediefined here
return this.notSynced;
},
sync: function (method, model, options) {
options || (options = {});
var errorHandler = {
error: function (jqXHR, textStatus, errorThrown) {
// TODO: put your error handling code here
// If you use the JS client from the different domain
// (f.e. locally) then Cross-origin resource sharing
// headers has to be set on the REST server side.
// Otherwise the JS client has to be copied into the
// some (f.e. the same) Web project on the same domain
alert('Unable to fulfil the request');
}
};
if (method === 'create') {
options.url = 'http://localhost:8080/grupoItosWS/api/oficinas/';
}
var result = Backbone.sync(method, model, _.extend(options, errorHandler));
return result;
}
});
// Collection class for Oficina entities
models.OficinaCollection = Backbone.Collection.extend({
model: models.Oficina,
url: "http://localhost:8080/grupoItosWS/api/oficinas/",
sync: function (method, model, options) {
options || (options = {});
var errorHandler = {
error: function (jqXHR, textStatus, errorThrown) {
// TODO: put your error handling code here
// If you use the JS client from the different domain
// (f.e. locally) then Cross-origin resource sharing
// headers has to be set on the REST server side.
// Otherwise the JS client has to be copied into the
// some (f.e. the same) Web project on the same domain
alert('Unable to fulfil the request');
}
};
var result = Backbone.sync(method, model, _.extend(options, errorHandler));
return result;
}
});
})(app.module("models"));
(function (views) {
views.ListView = Backbone.View.extend({
tagName: 'tbody',
initialize: function (options) {
this.options = options || {};
this.model.bind("reset", this.render, this);
var self = this;
this.model.bind("add", function (modelName) {
var row = new views.ListItemView({
model: modelName,
templateName: self.options.templateName
}).render().el;
$(self.el).append($(row));
$(self.el).parent().trigger('addRows', [$(row)]);
});
},
render: function (eventName) {
var self = this;
_.each(this.model.models, function (modelName) {
$(this.el).append(new views.ListItemView({
model: modelName,
templateName: self.options.templateName
}).render().el);
}, this);
return this;
}
});
views.ListItemView = Backbone.View.extend({
tagName: 'tr',
initialize: function (options) {
this.options = options || {};
this.model.bind("change", this.render, this);
this.model.bind("destroy", this.close, this);
},
template: function (json) {
/*
* templateName is element identifier in HTML
* $(this.options.templateName) is element access to the element
* using jQuery
*/
return _.template($(this.options.templateName).html())(json);
},
render: function (eventName) {
$(this.el).html(this.template(this.model.toJSON()));
return this;
},
close: function () {
var table = $(this.el).parent().parent();
table.trigger('disable.pager');
$(this.el).unbind();
$(this.el).remove();
table.trigger('enable.pager');
}
});
views.ModelView = Backbone.View.extend({
initialize: function (options) {
this.options = options || {};
this.model.bind("change", this.render, this);
},
render: function (eventName) {
$(this.el).html(this.template(this.model.toJSON()));
return this;
},
template: function (json) {
/*
* templateName is element identifier in HTML
* $(this.options.templateName) is element access to the element
* using jQuery
*/
return _.template($(this.options.templateName).html())(json);
},
/*
* Classes "save" and "delete" are used on the HTML controls to listen events.
* So it is supposed that HTML has controls with these classes.
*/
events: {
"change input": "change",
"click .save": "save",
"click .delete": "drop"
},
change: function (event) {
var target = event.target;
console.log('changing ' + target.id + ' from: ' + target.defaultValue + ' to: ' + target.value);
},
save: function () {
// TODO : put save code here
var hash = this.options.getHashObject();
this.model.set(hash);
if (this.model.isNew() && this.collection) {
var self = this;
this.collection.create(this.model, {
success: function () {
// see isNew() method implementation in the model
self.model.notSynced = false;
self.options.navigate(self.model.id);
}
});
} else {
this.model.save();
this.model.el.parent().parent().trigger("update");
}
return false;
},
drop: function () {
this.model.destroy({
success: function () {
/*
* TODO : put your code here
* f.e. alert("Model is successfully deleted");
*/
window.history.back();
}
});
return false;
},
close: function () {
$(this.el).unbind();
$(this.el).empty();
}
});
// This view is used to create new model element
views.CreateView = Backbone.View.extend({
initialize: function (options) {
this.options = options || {};
this.render();
},
render: function (eventName) {
$(this.el).html(this.template());
return this;
},
template: function (json) {
/*
* templateName is element identifier in HTML
* $(this.options.templateName) is element access to the element
* using jQuery
*/
return _.template($(this.options.templateName).html())(json);
},
/*
* Class "new" is used on the control to listen events.
* So it is supposed that HTML has a control with "new" class.
*/
events: {
"click .new": "create"
},
create: function (event) {
this.options.navigate();
return false;
}
});
})(app.module("views"));
$(function () {
var models = app.module("models");
var views = app.module("views");
var AppRouter = Backbone.Router.extend({
routes: {
'': 'list',
'new': 'create'
,
':id': 'details'
},
initialize: function () {
var self = this;
$('#create').html(new views.CreateView({
// tpl-create is template identifier for 'create' block
templateName: '#tpl-create',
navigate: function () {
self.navigate('new', true);
}
}).render().el);
},
list: function () {
this.collection = new models.OficinaCollection();
var self = this;
this.collection.fetch({
success: function () {
self.listView = new views.ListView({
model: self.collection,
// tpl-oficina-list-itemis template identifier for item
templateName: '#tpl-oficina-list-item'
});
$('#datatable').html(self.listView.render().el).append(_.template($('#thead').html())());
if (self.requestedId) {
self.details(self.requestedId);
}
var pagerOptions = {
// target the pager markup
container: $('.pager'),
// output string - default is '{page}/{totalPages}'; possiblevariables: {page}, {totalPages},{startRow}, {endRow} and {totalRows}
output: '{startRow} to {endRow} ({totalRows})',
// starting page of the pager (zero based index)
page: 0,
// Number of visible rows - default is 10
size: 10
};
$('#datatable').tablesorter({widthFixed: true,
widgets: ['zebra']}).
tablesorterPager(pagerOptions);
}
});
},
details: function (id) {
if (this.collection) {
this.oficina = this.collection.get(id);
if (this.view) {
this.view.close();
}
var self = this;
this.view = new views.ModelView({
model: this.oficina,
// tpl-oficina-details is template identifier for chosen model element
templateName: '#tpl-oficina-details',
getHashObject: function () {
return self.getData();
}
});
$('#details').html(this.view.render().el);
} else {
this.requestedId = id;
this.list();
}
},
create: function () {
if (this.view) {
this.view.close();
}
var self = this;
var dataModel = new models.Oficina();
// see isNew() method implementation in the model
dataModel.notSynced = true;
this.view = new views.ModelView({
model: dataModel,
collection: this.collection,
// tpl-oficina-details is a template identifier for chosen model element
templateName: '#tpl-oficina-details',
navigate: function (id) {
self.navigate(id, false);
},
getHashObject: function () {
return self.getData();
}
});
$('#details').html(this.view.render().el);
},
getData: function () {
return {
idOficina: $('#idOficina').val(),
descripcion: $('#descripcion').val(),
numero: $('#numero').val()
};
}
});
new AppRouter();
Backbone.history.start();
});
我一直在寻找解决方案,但我没有找到它,另外这个错误对我来说很神秘,因为我对 JAX-RS 或 Backbone 不太了解。以前有人遇到过这个问题吗?有什么解决办法吗?