diff --git a/java/hibernate/Makefile b/java/hibernate/Makefile index 29675d4aec..4f6495d21c 100644 --- a/java/hibernate/Makefile +++ b/java/hibernate/Makefile @@ -15,6 +15,10 @@ # # Author: Nathan VanBenschoten (nvanbenschoten@gmail.com) +ifneq ($(ADDR),) +ADDRFLAG = -PappArgs="['-addr', '$(ADDR)']" +endif + .PHONY: start start: - @gradle run + @gradle run $(ADDRFLAG) diff --git a/java/hibernate/build.gradle b/java/hibernate/build.gradle index 0d06242ec4..106402f566 100644 --- a/java/hibernate/build.gradle +++ b/java/hibernate/build.gradle @@ -5,15 +5,27 @@ apply plugin: 'java' apply plugin: 'application' mainClassName = 'com.cockroachlabs.Application' -sourceCompatibility = 1.7 repositories { mavenCentral() } dependencies { + // Necessary for Hibernate. compile 'org.hibernate:hibernate-core:5.2.4.Final' compile 'org.postgresql:postgresql:9.4.1208' + // Necessary for web application. + compile 'org.glassfish.jersey.core:jersey-server:2.25' + compile 'org.glassfish.jersey.containers:jersey-container-netty-http:2.25' + compile 'com.fasterxml.jackson.core:jackson-databind:2.8.5' + compile 'com.beust:jcommander:1.7' + testCompile group: 'junit', name: 'junit', version: '4.11' } + +run { + if (project.hasProperty("appArgs")) { + args Eval.me(appArgs) + } +} diff --git a/java/hibernate/src/main/java/com/cockroachlabs/Application.java b/java/hibernate/src/main/java/com/cockroachlabs/Application.java index f71ad65b4f..e2e6d266d7 100644 --- a/java/hibernate/src/main/java/com/cockroachlabs/Application.java +++ b/java/hibernate/src/main/java/com/cockroachlabs/Application.java @@ -1,48 +1,47 @@ package com.cockroachlabs; -import com.cockroachlabs.model.Customer; -import com.cockroachlabs.model.Order; -import com.cockroachlabs.model.Product; -import org.hibernate.Session; -import org.hibernate.SessionFactory; -import org.hibernate.boot.registry.StandardServiceRegistryBuilder; -import org.hibernate.cfg.Configuration; -import org.hibernate.service.ServiceRegistry; - -import java.math.BigDecimal; +import com.beust.jcommander.JCommander; +import com.beust.jcommander.Parameter; +import com.cockroachlabs.services.CustomerService; +import com.cockroachlabs.services.OrderService; +import com.cockroachlabs.services.PingService; +import com.cockroachlabs.services.ProductService; +import com.cockroachlabs.util.SessionUtil; +import org.glassfish.jersey.netty.httpserver.NettyHttpContainerProvider; +import org.glassfish.jersey.server.ResourceConfig; + +import javax.ws.rs.core.UriBuilder; +import java.net.URI; public class Application { - public static void main(String[] args) { - try (SessionFactory sf = buildSessionFactory()) { - Session session = sf.getCurrentSession(); - - Customer c = new Customer(); - c.setName("joe"); - - Order o = new Order(); - o.setCustomer(c); - o.setSubtotal(new BigDecimal(100)); + @Parameter(names = "-addr", description = "the address of the database") + private String dbAddr; - session.beginTransaction(); - session.save(c); - session.save(o); - session.getTransaction().commit(); - } + public static void main(String[] args) { + Application app = new Application(); + new JCommander(app, args); + app.run(); } - private static SessionFactory buildSessionFactory() { - Configuration configuration = new Configuration(); - configuration.configure("hibernate.cfg.xml"); - configuration.addAnnotatedClass(Customer.class); - configuration.addAnnotatedClass(Order.class); - configuration.addAnnotatedClass(Product.class); + private void run() { + initHibernate(); + initHTTPServer(); + } - ServiceRegistry serviceRegistry = new StandardServiceRegistryBuilder() - .applySettings(configuration.getProperties()) - .build(); + private void initHibernate() { + SessionUtil.init(dbAddr); + } - return configuration.buildSessionFactory(serviceRegistry); + private void initHTTPServer() { + URI baseUri = UriBuilder.fromUri("http://localhost/").port(6543).build(); + ResourceConfig resourceConfig = new ResourceConfig( + PingService.class, + CustomerService.class, + ProductService.class, + OrderService.class + ); + NettyHttpContainerProvider.createServer(baseUri, resourceConfig, true); } } diff --git a/java/hibernate/src/main/java/com/cockroachlabs/model/Order.java b/java/hibernate/src/main/java/com/cockroachlabs/model/Order.java index 46b1f0a71c..0e3fde4abe 100644 --- a/java/hibernate/src/main/java/com/cockroachlabs/model/Order.java +++ b/java/hibernate/src/main/java/com/cockroachlabs/model/Order.java @@ -21,7 +21,7 @@ public class Order { private Customer customer; @ManyToMany() - @JoinTable(name="product_orders", + @JoinTable(name="order_products", joinColumns=@JoinColumn(name="order_id"), inverseJoinColumns=@JoinColumn(name="product_id")) private Set products; diff --git a/java/hibernate/src/main/java/com/cockroachlabs/model/Product.java b/java/hibernate/src/main/java/com/cockroachlabs/model/Product.java index c499873856..a1ab56df9b 100644 --- a/java/hibernate/src/main/java/com/cockroachlabs/model/Product.java +++ b/java/hibernate/src/main/java/com/cockroachlabs/model/Product.java @@ -16,8 +16,8 @@ public class Product { @Column(name="name") private String name; - @Column(name="product", precision=18, scale=2) - private BigDecimal product; + @Column(name="price", precision=18, scale=2) + private BigDecimal price; @ManyToMany(cascade=CascadeType.ALL, mappedBy="products") private Set orders; @@ -38,12 +38,12 @@ public void setName(String name) { this.name = name; } - public BigDecimal getProduct() { - return product; + public BigDecimal getPrice() { + return price; } - public void setProduct(BigDecimal product) { - this.product = product; + public void setPrice(BigDecimal price) { + this.price = price; } public Set getOrders() { diff --git a/java/hibernate/src/main/java/com/cockroachlabs/services/CustomerService.java b/java/hibernate/src/main/java/com/cockroachlabs/services/CustomerService.java new file mode 100644 index 0000000000..4c3ec796cc --- /dev/null +++ b/java/hibernate/src/main/java/com/cockroachlabs/services/CustomerService.java @@ -0,0 +1,100 @@ +package com.cockroachlabs.services; + +import com.cockroachlabs.model.Customer; +import com.cockroachlabs.util.SessionUtil; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.hibernate.Session; +import org.hibernate.Transaction; +import org.hibernate.query.Query; + +import javax.ws.rs.*; +import java.io.IOException; +import java.util.List; + +@Path("/customer") +public class CustomerService { + + private final ObjectMapper mapper = new ObjectMapper(); + + @GET + @Produces("application/json") + public String getCustomers() { + try (Session session = SessionUtil.getSession()) { + Query query = session.createQuery("from Customer"); + List customers = query.list(); + return mapper.writeValueAsString(customers); + } catch (JsonProcessingException e) { + return e.toString(); + } + } + + @POST + @Produces("application/json") + public String createCustomer(String body) { + try (Session session = SessionUtil.getSession()) { + Customer newCustomer = mapper.readValue(body, Customer.class); + session.save(newCustomer); + + return mapper.writeValueAsString(newCustomer); + } catch (IOException e) { + return e.toString(); + } + } + + @GET + @Path("/{customerID}") + @Produces("application/json") + public String getCustomer(@PathParam("customerID") long customerID) { + try (Session session = SessionUtil.getSession()) { + Customer customer = session.get(Customer.class, customerID); + if (customer == null) { + throw new NotFoundException(); + } + + return mapper.writeValueAsString(customer); + } catch (JsonProcessingException e) { + return e.toString(); + } + } + + @PUT + @Path("/{customerID}") + @Produces("application/json") + public String updateCustomer(@PathParam("customerID") long customerID, String body) { + try (Session session = SessionUtil.getSession()) { + Customer updateInfo = mapper.readValue(body, Customer.class); + updateInfo.setId(customerID); + + Customer updatedCustomer = (Customer) session.merge(updateInfo); + return mapper.writeValueAsString(updatedCustomer); + } catch (IOException e) { + return e.toString(); + } + } + + @DELETE + @Path("/{customerID}") + @Produces("text/plain") + public String deleteCustomer(@PathParam("customerID") long customerID) { + try (Session session = SessionUtil.getSession()) { + Transaction tx = session.beginTransaction(); + + Query deleteReferencing = session.createQuery("delete from Order where customer_id = :id"); + deleteReferencing.setParameter("id", customerID); + deleteReferencing.executeUpdate(); + + Query deleteCustomer = session.createQuery("delete from Customer where id = :id"); + deleteCustomer.setParameter("id", customerID); + + int rowCount = deleteCustomer.executeUpdate(); + if (rowCount == 0) { + tx.rollback(); + throw new NotFoundException(); + } + tx.commit(); + return "ok"; + } + } + +} diff --git a/java/hibernate/src/main/java/com/cockroachlabs/services/OrderService.java b/java/hibernate/src/main/java/com/cockroachlabs/services/OrderService.java new file mode 100644 index 0000000000..b2a87d9694 --- /dev/null +++ b/java/hibernate/src/main/java/com/cockroachlabs/services/OrderService.java @@ -0,0 +1,100 @@ +package com.cockroachlabs.services; + +import com.cockroachlabs.model.Order; +import com.cockroachlabs.util.SessionUtil; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.hibernate.Session; +import org.hibernate.Transaction; +import org.hibernate.query.Query; + +import javax.ws.rs.*; +import java.io.IOException; +import java.util.List; + +@Path("/order") +public class OrderService { + + private final ObjectMapper mapper = new ObjectMapper(); + + @GET + @Produces("application/json") + public String getOrders() { + try (Session session = SessionUtil.getSession()) { + Query query = session.createQuery("from Order"); + List orders = query.list(); + return mapper.writeValueAsString(orders); + } catch (JsonProcessingException e) { + return e.toString(); + } + } + + @POST + @Produces("application/json") + public String createOrder(String body) { + try (Session session = SessionUtil.getSession()) { + Order newOrder = mapper.readValue(body, Order.class); + session.save(newOrder); + + return mapper.writeValueAsString(newOrder); + } catch (IOException e) { + return e.toString(); + } + } + + @GET + @Path("/{orderID}") + @Produces("application/json") + public String getOrder(@PathParam("orderID") long orderID) { + try (Session session = SessionUtil.getSession()) { + Order order = session.get(Order.class, orderID); + if (order == null) { + throw new NotFoundException(); + } + + return mapper.writeValueAsString(order); + } catch (JsonProcessingException e) { + return e.toString(); + } + } + + @PUT + @Path("/{orderID}") + @Produces("application/json") + public String updateOrder(@PathParam("orderID") long orderID, String body) { + try (Session session = SessionUtil.getSession()) { + Order updateInfo = mapper.readValue(body, Order.class); + updateInfo.setId(orderID); + + Order updatedOrder = (Order) session.merge(updateInfo); + return mapper.writeValueAsString(updatedOrder); + } catch (IOException e) { + return e.toString(); + } + } + + @DELETE + @Path("/{orderID}") + @Produces("text/plain") + public String deleteOrder(@PathParam("orderID") long orderID) { + try (Session session = SessionUtil.getSession()) { + Transaction tx = session.beginTransaction(); + + Query deleteReferencing = session.createQuery("delete from Order where order_id = :id"); + deleteReferencing.setParameter("id", orderID); + deleteReferencing.executeUpdate(); + + Query deleteOrder = session.createQuery("delete from Order where id = :id"); + deleteOrder.setParameter("id", orderID); + + int rowCount = deleteOrder.executeUpdate(); + if (rowCount == 0) { + tx.rollback(); + throw new NotFoundException(); + } + tx.commit(); + return "ok"; + } + } + +} diff --git a/java/hibernate/src/main/java/com/cockroachlabs/services/PingService.java b/java/hibernate/src/main/java/com/cockroachlabs/services/PingService.java new file mode 100644 index 0000000000..fedaad909b --- /dev/null +++ b/java/hibernate/src/main/java/com/cockroachlabs/services/PingService.java @@ -0,0 +1,16 @@ +package com.cockroachlabs.services; + +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; + +@Path("/ping") +public class PingService { + + @GET + @Produces("text/plain") + public String ping() { + return "pong"; + } + +} diff --git a/java/hibernate/src/main/java/com/cockroachlabs/services/ProductService.java b/java/hibernate/src/main/java/com/cockroachlabs/services/ProductService.java new file mode 100644 index 0000000000..e25785a48a --- /dev/null +++ b/java/hibernate/src/main/java/com/cockroachlabs/services/ProductService.java @@ -0,0 +1,100 @@ +package com.cockroachlabs.services; + +import com.cockroachlabs.model.Product; +import com.cockroachlabs.util.SessionUtil; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.hibernate.Session; +import org.hibernate.Transaction; +import org.hibernate.query.Query; + +import javax.ws.rs.*; +import java.io.IOException; +import java.util.List; + +@Path("/product") +public class ProductService { + + private final ObjectMapper mapper = new ObjectMapper(); + + @GET + @Produces("application/json") + public String getProducts() { + try (Session session = SessionUtil.getSession()) { + Query query = session.createQuery("from Product"); + List products = query.list(); + return mapper.writeValueAsString(products); + } catch (JsonProcessingException e) { + return e.toString(); + } + } + + @POST + @Produces("application/json") + public String createProduct(String body) { + try (Session session = SessionUtil.getSession()) { + Product newProduct = mapper.readValue(body, Product.class); + session.save(newProduct); + + return mapper.writeValueAsString(newProduct); + } catch (IOException e) { + return e.toString(); + } + } + + @GET + @Path("/{productID}") + @Produces("application/json") + public String getProduct(@PathParam("productID") long productID) { + try (Session session = SessionUtil.getSession()) { + Product product = session.get(Product.class, productID); + if (product == null) { + throw new NotFoundException(); + } + + return mapper.writeValueAsString(product); + } catch (JsonProcessingException e) { + return e.toString(); + } + } + + @PUT + @Path("/{productID}") + @Produces("application/json") + public String updateProduct(@PathParam("productID") long productID, String body) { + try (Session session = SessionUtil.getSession()) { + Product updateInfo = mapper.readValue(body, Product.class); + updateInfo.setId(productID); + + Product updatedProduct = (Product) session.merge(updateInfo); + return mapper.writeValueAsString(updatedProduct); + } catch (IOException e) { + return e.toString(); + } + } + + @DELETE + @Path("/{productID}") + @Produces("text/plain") + public String deleteProduct(@PathParam("productID") long productID) { + try (Session session = SessionUtil.getSession()) { + Transaction tx = session.beginTransaction(); + + Query deleteReferencing = session.createQuery("delete from Order where product_id = :id"); + deleteReferencing.setParameter("id", productID); + deleteReferencing.executeUpdate(); + + Query deleteProduct = session.createQuery("delete from Product where id = :id"); + deleteProduct.setParameter("id", productID); + + int rowCount = deleteProduct.executeUpdate(); + if (rowCount == 0) { + tx.rollback(); + throw new NotFoundException(); + } + tx.commit(); + return "ok"; + } + } + +} diff --git a/java/hibernate/src/main/java/com/cockroachlabs/util/SessionUtil.java b/java/hibernate/src/main/java/com/cockroachlabs/util/SessionUtil.java new file mode 100644 index 0000000000..08792f0002 --- /dev/null +++ b/java/hibernate/src/main/java/com/cockroachlabs/util/SessionUtil.java @@ -0,0 +1,79 @@ +package com.cockroachlabs.util; + +import com.cockroachlabs.model.Customer; +import com.cockroachlabs.model.Order; +import com.cockroachlabs.model.Product; +import org.hibernate.Session; +import org.hibernate.SessionFactory; +import org.hibernate.boot.registry.StandardServiceRegistryBuilder; +import org.hibernate.cfg.Configuration; +import org.hibernate.service.ServiceRegistry; + +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class SessionUtil { + + private static SessionUtil instance; + + private SessionFactory sessionFactory; + + private SessionUtil(String dbAddr) { + if (dbAddr != null) { + // When testing, anything output to stderr will fail the test, so we only + // log severe messages that indicate serious failure. + Logger.getLogger("org.hibernate").setLevel(Level.SEVERE); + } + + Configuration configuration = new Configuration(); + configuration.configure("hibernate.cfg.xml"); + configuration.addAnnotatedClass(Customer.class); + configuration.addAnnotatedClass(Order.class); + configuration.addAnnotatedClass(Product.class); + + if (dbAddr != null) { + // Most drivers expect the user in a connection to be specified like: + // postgresql://@host:port/db + // but the PGJDBC expects the user as a parameter like: + // postgresql://host:port/db?user= + Pattern p = Pattern.compile("postgresql://((\\w+)@).*"); + Matcher m = p.matcher(dbAddr); + if (m.matches()) { + String userPart = m.group(1); + String user = m.group(2); + + + String sep = "?"; + if (dbAddr.contains("?")) { + sep = "&"; + } + dbAddr = String.format("%s%suser=%s", dbAddr.replace(userPart, ""), sep, user); + } + + // Add the "jdbc:" prefix to the address and replace in configuration. + dbAddr = "jdbc:" + dbAddr; + configuration.setProperty("hibernate.connection.url", dbAddr); + } + + ServiceRegistry serviceRegistry = new StandardServiceRegistryBuilder() + .applySettings(configuration.getProperties()) + .build(); + + sessionFactory = configuration.buildSessionFactory(serviceRegistry); + } + + public static void init(String dbAddr) { + instance = new SessionUtil(dbAddr); + } + + private static SessionUtil getInstance() { + return instance; + } + + public static Session getSession(){ + return getInstance().sessionFactory.openSession(); + } + +} diff --git a/java/hibernate/src/main/resources/hibernate.cfg.xml b/java/hibernate/src/main/resources/hibernate.cfg.xml index 9390d7f814..ae4e824c18 100644 --- a/java/hibernate/src/main/resources/hibernate.cfg.xml +++ b/java/hibernate/src/main/resources/hibernate.cfg.xml @@ -9,12 +9,11 @@ org.postgresql.Driver - jdbc:postgresql://127.0.0.1:26257/company?sslmode=disable + jdbc:postgresql://127.0.0.1:26257/company_hibernate?sslmode=disable root - - 1 + 16 org.hibernate.dialect.PostgreSQL94Dialect diff --git a/testing/main_test.go b/testing/main_test.go index 7d7ccc48b6..518fef4f83 100644 --- a/testing/main_test.go +++ b/testing/main_test.go @@ -219,3 +219,7 @@ func testORM(t *testing.T, language, orm string) { func TestGORM(t *testing.T) { testORM(t, "go", "gorm") } + +func TestHibernate(t *testing.T) { + testORM(t, "java", "hibernate") +}