Git Product home page Git Product logo

fluentjpa's Introduction

Get Back in Control of Your SQL with JPA
Patent Pending License Java Version Maven Central

FluentJPA is a Language Integrated Query (LINQ) technology for relational (SQL) databases and JPA. It allows you to use Java to write strongly typed queries by directly integrating into the language.

How does FluentJPA integrate into Java?

At first glance, it seems that we need a hook in the Java compiler. But in fact, we have full access to the compiled bytecode, which has all the necessary "knowledge". This is how FluentJPA does its magic - it reads the bytecode and translates it to SQL.

As a result, the integration is full, and FluentJPA supports all Java language constructs, including functions, variables, etc - anything the compiler can compile and also makes sense in the SQL context. See Java Language Support for details.

We already have JPA, JPA repositories and other technologies

FluentJPA seeks to complement JPA where the developer wants to gain control over SQL

If ORM aims to solve the problem of mapping the database schema into the Java object model; FluentJPA aims to allow writing SQL queries in Java using this object model. FluentJPA declares SQL clauses (like SELECT, FROM, WHERE) as first class Java methods, so the queries are visually similar:

// Java
FluentJPA.SQL((Person p) -> {
    SELECT(p);
    FROM(p);
    WHERE(p.getName() == name);
});
-- SQL
SELECT t0.*
FROM PERSON_TABLE t0
WHERE (t0.name = ?)

As a result, using FluentJPA you can write SQL without loss of type safety, intellisense, refactoring.

JPA Integration

FluentJPA reads JPA annotations to map entities to SQL table names and properties to column names. Then it uses JPA native query for execution. As a result the solution integrates with JPA pipeline and transactions, calls to JPA and FluentJPA can be mixed freely giving the correct results.

SQL Support

FluentJPA supports the entire modern SQL DML standard. In addition to SQL-92, where JPQL lives, FluentJPA supports SQL-99 Common Table Expressions (WITH clause), SQL-2003 Window Functions (OVER clause), SQL-2003 MERGE (UPSERT clause), Dynamic Queries without Criteria API and many, many more.

FluentJPA also supports proprietary SQL extensions provided by the 4 most popular databases, see static imports. Follow links in Basic/Advanced SQL DML Statements from the wiki sidebar to see examples.

  • All functions mapped to SQL counterparts follow SQL naming convention - capitals with underscores as delimiters. As a result your code looks like SQL, but is Java with intellisense and compiler validation!
  • All helper functions follow standard Java naming convention. They are either Library methods or Directives.

FluentJPA.SQL()

This is an "entry-point" method to the FluentJPA. It accepts a Java lambda and translates it to SQL query. There are few conventions:

  • Lambda parameters must be entity types. This way we declare the table references to be used in this query. Like in SQL, if there is a self join, there will be 2 parameters of the same entity type. For example:

    FluentQuery query = FluentJPA.SQL((Staff emp,
                                       Staff manager,
                                       Store store) -> {
        // returns store name, employee first name and its manager first name
        // ordered by store and manager
        SELECT(store.getName(), emp.getFirstName(), manager.getFirstName());
        FROM(emp).JOIN(manager).ON(emp.getManager() == manager)
                 .JOIN(store).ON(emp.getStore() == store);
        ORDER(BY(emp.getStore()), BY(emp.getManager()));
    
    });
    • In Java entity represents SQL Table or more generally a column set
    • Having entities as parameters makes clear which tables this query works on
  • Every time, where SQL expects a table reference (e.g. FROM), an entity should be passed. FluentJPA will read the required Table information via JPA annotations.

  • FluentJPA translates Lambda's body SQL clauses (written in Java) in the same order as they appear. Thus the content of the sample above is translated to exactly 3 lines:

    SELECT t2.store_name, t0.first_name, t1.first_name
    FROM staffs AS t0 INNER JOIN staffs AS t1 ON (t0.manager_id = t1.staff_id) INNER JOIN stores AS t2 ON (t0.store_id = t2.store_id)
    ORDER BY t0.store_id, t0.manager_id
  • Finally, call FluentQuery.createQuery() to get a standard JPA Query instance (see JPA Integration for details):

    TypedQuery<X> typedQuery = query.createQuery(entityManager, <X>.class);
    // execute the query
    typedQuery.getResultList(); // or getSingleResult() / executeUpdate()

Setup

There is no bootstrap, code generation step or anything else needed to use FluentJPA. Add dependencies to your project enjoy the type-safe Object Oriented SQL in your JPA project without compromises! (Disclaimer: FluentJPA strives to be as unobtrusive as possible. We don't change or affect anything, so your existing code will continue to work as before. We don't bring any dependencies except our own code and ASM, total ~500K).

Usage Examples

Probably the most important feature missing in JPA is Sub Query. We think that any serious SQL starts with them (just look here for few examples). Not only FluentJPA supports sub queries, it also lets put them into a separate Java(!) function. So the code looks 100% natural to a Java developer.

Let's start with the simplest query possible to overview the entire flow. (A bit spiced with passing an external parameter and optional JPA Repository integration)

@Repository
public interface PersonRepository extends CrudRepository<Person, Long>, EntityManagerSupplier {

    default List<Person> getAllByName(String name) {
        FluentQuery query = FluentJPA.SQL((Person p) -> {
            SELECT(p);
            FROM(p);
            WHERE(p.getName() == name);
        });

        return query.createQuery(getEntityManager(), Person.class).getResultList();
    }
}

SQL query that gets generated, name is passed via a parameter:

SELECT t0.*
FROM PERSON_TABLE t0
WHERE (t0.name = ?)

FluentJPA supports any query, here we brought few examples with sub queries to show the power of FluentJPA. There is a link to the test file source code and a link to the original SQL where we borrowed the use case from. Best when seen side-by-side.

1 sub query "converted" to a Java function (original SQL comes from SQL Server documentation).

Citing original docs: This example finds the product models for which the maximum list price is more than twice the average for the model.

// Product is a standard JPA Entity
FluentQuery query = FluentJPA.SQL((Product p1) -> {

    SELECT(p1.getModel());
    FROM(p1);
    GROUP(BY(p1.getModel()));
    HAVING(MAX(p1.getListPrice()) >= ALL(avgPriceForProductModel(p1.getModel())));
    // sub query in SQL, function in Java ^^^^^^^^^^^^^^^^^^^^
});

...

// The result is an int since the sub query returns 1 row/column
private static int avgPriceForProductModel(ProductModel model) {
    return subQuery((Product p2) -> {
        SELECT(AVG(p2.getListPrice()));
        FROM(p2);
        WHERE(model == p2.getModel());
    });
}

3 sub queries "converted" to functions (original SQL comes from SQL Server documentation).

// Arguments are automatically captured and passed in via JPA's Query.setParameter()
String orderDate; // passed by an external parameter

FluentQuery query = FluentJPA.SQL(() -> {

    // returns an entity!
    SalesOrderDetail sales = salesByProducts(orderDate);

    // previous result is an argument for the next function
    Change change = updateInventoryWithSales(sales);

    trackNoInventory(change);
});

...

// the result is SalesOrderDetail since the SELECTed columns are aliased to its fields
private static SalesOrderDetail salesByProducts(String orderDate) {

    return subQuery((SalesOrderDetail sod,
                        SalesOrderHeader soh) -> {

        // since the function returns SalesOrderDetail, alias
        // SELECTed columns to SalesOrderDetail's fields (type safety is kept)
        Product product = alias(sod.getProduct(), SalesOrderDetail::getProduct);
        int orderQty = alias(SUM(sod.getOrderQty()), SalesOrderDetail::getOrderQty);

        SELECT(product, orderQty);
        FROM(sod).JOIN(soh)
                 .ON(sod.getSalesOrderID() == soh.getSalesOrderID() && soh.getOrderDate() == orderDate);
        GROUP(BY(product));
    });
}

private static Change updateInventoryWithSales(SalesOrderDetail order) {

    return subQuery((ProductInventory inv) -> {

        ProductInventory deleted = DELETED();

        MERGE().INTO(inv).USING(order).ON(inv.getProduct() == order.getProduct());
        // Non foreign key Object JOIN -----------------^^^^^^^^

        WHEN_MATCHED_AND(inv.getQuantity() - order.getOrderQty() <= 0).THEN(DELETE());

        WHEN_MATCHED().THEN(MERGE_UPDATE().SET(() -> {
            inv.setQuantity(inv.getQuantity() - order.getOrderQty());
        }));

        // since the function returns Change, alias
        // OUTPUTed columns to Change's fields
        MergeAction action = alias($action(), Change::getAction);
        int productID = alias(deleted.getProduct().getProductID(), Change::getProductID);
        OUTPUT(action, productID);
    });
}

private static void trackNoInventory(Change change) {

    subQuery((ZeroInventory zi) -> {

        INSERT().INTO(viewOf(zi, ZeroInventory::getDeletedProductID, ZeroInventory::getRemovedOnDate));

        SELECT(change.getProductID(), GETDATE());
        FROM(change);
        WHERE(change.getAction() == MergeAction.DELETE);
    });
}

Recursive sub query (original SQL comes from PostgreSQL documentation).

Citing original docs: This query would remove all direct and indirect subparts of a product.
If you don't know what recursive WITH is, it's worth learning. Since you will be able to use it now with FluentJPA ๐Ÿ˜‰.

FluentJPA.SQL((Part allParts) -> {

    Part included_parts = subQuery((Part it,
                                    Part parts,
                                    Part subParts) -> {
        // initial
        SELECT(parts.getSubPart(), parts.getName());
        FROM(parts);
        WHERE(parts.getName() == "our_product");

        UNION_ALL();

        // recursive
        SELECT(subParts.getSubPart(), subParts.getName());

        // recurse
        FROM(recurseOn(it), subParts);
        WHERE(it.getSubPart() == subParts.getName());
    });

    WITH(RECURSIVE(included_parts));

    DELETE().FROM(allParts);
    WHERE(collect(included_parts, included_parts.getName()).contains(allParts.getName()));

});

Example 4 - getByNameLike()

Dynamic Queries without Criteria API:

// build the criteria dynamically
Function1<CoverageMaster, Boolean> dynamicFilter = buildOr1(likes);

FluentQuery query = FluentJPA.SQL((UtilizationDTL util,
                                   UtilizationCoverageDTL utilizationCover,
                                   CoverageMaster coverMaster) -> {
    SELECT(DISTINCT(util.getId()));
    FROM(util).JOIN(utilizationCover)
              .ON(utilizationCover.getUtilization() == util)
              .JOIN(coverMaster)
              .ON(utilizationCover.getMaster() == coverMaster);

    WHERE(dynamicFilter.apply(coverMaster) && util.isCompleted());
      //  ^^^^^^^^^^^^^^^^^^^--- inject the criteria,
      //                         rest of the query is unaffected
    ORDER(BY(util.getId()));
});

private Function1<CoverageMaster, Boolean> buildOr1(List<String> likes) {
    Function1<CoverageMaster, Boolean> criteria = Function1.FALSE();

    for (String like : likes)
        criteria = criteria.or(p -> p.getCoverageName().toLowerCase()
                                                       .matches(parameter(like)));

    return criteria;
}

Full documentation

License

This work is dual-licensed under Affero GPL 3.0 and Lesser GPL 3.0. The source code is licensed under AGPL and official binaries under LGPL.

Therefore the library can be used in commercial projects.

SPDX-License-Identifier: AGPL-3.0-only AND LGPL-3.0-only

fluentjpa's People

Contributors

kostat avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

fluentjpa's Issues

Licensing

For example, if you are running FluentJPA on your server, to provide a SaaS services, you would have to give away all of your source code.

I think this does not hold, since your specific software is not meant to be used by a user over a network. Pls. double check. Also read the page you refer to:

https://www.gnu.org/licenses/why-affero-gpl.html

Which links to:
https://www.gnu.org/philosophy/who-does-that-server-really-serve.html

itext is an interesting example of an APGL library, where the AGPL approach for dual licensing seems to work. Maybe their approach works out since the (end) user gets the PDF and generating PDFs is an explicit function of the including software. So there is user interaction for the purpose of generating the PDF.

I recommend to release the basic product simply under Apache and make money with addons, support and training. Even GPL is too restrictive and hinders adoption.

Sorry to bring this up as first issue. But I wouldn't make the effort, if I didn't see any potential here ;)

Support of Generics

I'm trying to implement a query with generic type in a base repository.

public List<T> getByName(String name) {
    FluentQuery query = FluentJPA.SQL((T animal) -> {
        SELECT(animal);
        FROM(animal);
    });

    Stream<?> stream = query.createQuery(getEntityManager()).getResultStream();
    return stream.map(x -> (T) x)
            .collect(Collectors.toList());
}

But instead of the concrete @entity type the @MappedSuperclass is used.

org.springframework.dao.InvalidDataAccessApiUsageException: Cannot calculate table reference from: class com.github.tomschi.fluentjpa.genericissue.AbstractAnimal; nested exception is java.lang.IllegalStateException: Cannot calculate table reference from: class com.github.tomschi.fluentjpa.genericissue.AbstractAnimal

Full example: https://github.com/Tomschi/fluentjpa-issues

Is this an issue?

Error when comping code with Java 9+ modular system

I am using Java 11 to write a desktop application that uses hibernation as JPA provider.

When I try to compile the code I get this error:
Modules jpa.notations and fluent.jpa export package co.streamx.fluent.JPA.spi to module java.persistence
my `module-info.java' file:

module org.sarc.ekrima.geany {
    requires javafx.fxml;
    requires javafx.controls;
    requires simplefx;
    requires java.persistence;
    requires com.h2database;
    requires java.sql;
    requires org.hibernate.orm.core;
    requires jpa.notations;
    requires fluent.jpa;
    requires sql.grammar;
    requires jdk.xml.dom;
    requires javafx.swing;
    requires io.nayuki.qrcodegen;
    requires io.github.classgraph;
    exports org.sarc.ekrima.geany.ui to simplefx;
    opens org.sarc.ekrima.geany.ui to javafx.fxml;
    exports org.sarc.ekrima.geany.backend.entities to org.hibernate.orm.core;
    exports org.sarc.ekrima.geany;
}

The error is caused by 2 lines:

    requires jpa.notations;
    requires fluent.jpa;

Secondary packaging

I don't want to write FROM and SELECT every time. Can we packaging FluentJPA.SQL again. Only pass the WHERE condition and return a complete SQL statement

Unable to import subQuery() method

After going through the FluentJPA Implementation on DZone. I was able to import all the statements like FluentJPA, FluentSQL, But I was unable to import subQuery into my program we have installed only fluent-jpa

So we need to import another maven package to access subQuery method??

How to resolve this issue??

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.