Giter VIP home page Giter VIP logo

sansorm's Introduction

SansORM

Preface

Even if you do "pure JDBC", you will find SansOrm's utility classes extremely useful. SansOrm is a "No-ORM" sane Java-to-SQL/SQL-to-Java object mapping library. It was created to rid my company's product of Hibernate. After about 10 years of using ORMs in various projects, I came to the same conclusion as others: ORM is an Anti-Pattern.

TL;DR:

  • Standard ORMs do not scale.
  • Don't fear the SQL.
  • What are you, lazy? Read the page.

SansOrm

SansOrm is not an ORM. SansOrm library will...

  • Massively decrease the boilerplate code you write even if you use pure SQL (and no Java objects)
  • Persist and retrieve simple annotated Java objects, and lists thereof, without you writing SQL
  • Persist and retrieve complex annotated Java objects, and lists thereof, where you provide the SQL

SansOrm will never...

  • Perform a JOIN for you
  • Persist a graph of objects for you
  • Lazily retrieve anything for you
  • Page data for you

These things that SansOrm will never do are better and more efficiently performed by you. SansOrm will help you do them simply, but there isn't much magic under the covers.

You could consider the philosophy of SansOrm to be SQL-first. That is, think about a correct SQL relational schema first, and then once that is correct, consider how to use SansOrm to make your life easier. In order to scale, your SQL schema design and the queries that run against it need to be efficient. There is no way to go from an "object model" to SQL with any kind of real efficiency, due to an inherent mis-match between the "object world" and the "relational world". As others have noted, if you truly need to develop in the currency of pure objects, then what you need is not a relational database but instead an object database.

Note: SansOrm does not currently support MySQL because the MySQL JDBC driver does not return proper metadata which is required by SansOrm for mapping. In the future, SansOrm may support a purely 100% annotation-based type mapping but this would merely be a concession to MySQL and in no way desirable.



Initialization

First of all we need a datasource. Once you get it, call one of SansOrm.initializeXXX methods:

DataSource ds = ...;
SansOrm.initializeTxNone(ds);

// or if you want to use embedded TransactionManager implementation
SansOrm.initializeTxSimple(ds);

// or if you have your own TransactionManager and UserTransaction
TransactionManager tm = ...;
UserTransaction ut = ...;
SansOrm.initializeTxCustom(ds, tm, ut);

We strongly recommend using the embedded TransactionManager via the the second initializer above. If you have an existing external TransactionManager, of course you can use that.

The embedded TransactionManager conserves database Connections when nested methods are called, alleviating the need to pass Connection instances around manually. For example:

List<User> getUsers(String lastNamePrefix) {
   return SqlClosure.sqlExecute( connection -> {       // <-- Transaction started, Connection #1 acquired.
      final List<Users> users =
         OrmElf.listFromClause(connection, User.class, "last_name LIKE ?", lastNamePrefix + "%");

      return populateRoles(users);
   }
   // Transaction automatically committed at the end of the execute() call.
}

List<User> populatePermissions(final List<User> users) {
   return SqlClosure.sqlExecute( connection -> {       // <-- Transaction in-progress, Connection #1 re-used.
      for (User user : users) {
         user.setPermissions(OrmElf.listFromClause(connection, Permission.class, "user_id=?", user.getId());
      }
      return users;
   }
   // Transaction will be committed at the end of the execute() call in getUsers() above.
}

The TransactionManager uses a ThreadLocal variable to "flow" the transaction across nested calls, allowing all work to be committed as a single unit of work. Additionally, Connection resources are conserved. Without a TransactionManager, the above code would require two Connections to be borrowed from a pool.

SqlClosure

We'll work from simple to complex. In the first examples, the savings in code will not seem that great, but as we go through the examples you'll notice the code using SansOrm vs. pure Java/JDBC gets more and more compact.

SansOrm provides you with two important classes. Let's look at the first, which has nothing to do with Java objects or persistence. This class just makes your life easier when writing raw SQL (JDBC). It is called SqlClosure.

Typical Java pure JDBC with [mostly] correct resource cleanup:

public int getUserCount(String usernameWildcard) throws SQLException {
   Connection connection = null;
   try {
      connection = dataSource.getConnection();
      PreparedStatement stmt = connection.prepareStatement("SELECT COUNT(*) FROM users WHERE username LIKE ?");
      stmt.setString(1, usernameWildcard);

      int count = 0;
      ResultSet resultSet = stmt.executeQuery();
      if (resultSet.next() {
         count = resultSet.getInt(1);
      }
      resultSet.close();
      stmt.close();
      return count;
   }
   finally {
      if (connection != null) {
         try {
            connection.close();
         }
         catch (SQLException e) {
            // ignore
         }
      }
   }
}

Now the same code using SansOrm's SqlClosure (with completely correct resource cleanup):

public int getUserCount(final String usernameWildcard) {
   return new SqlClosure<Integer>() {
      public Integer execute(Connection conn) {
          PreparedStatement stmt = conn.prepareStatement("SELECT COUNT(*) FROM users WHERE username LIKE ?");
          stmt.setString(1, usernameWildcard);
          ResultSet resultSet = stmt.executeQuery();
          return (resultSet.next() ? resultSet.getInt(1) : 0;
      }
   }.execute();
}

Important points:

  • The SqlClosure class is a generic (templated) class
  • The SqlClosure class will call your execute(Connection) method with a provided connection
    • The provided connection will be closed quietly automatically (i.e. exceptions in connection.close() will be eaten)
  • SqlExceptions thrown from the body of the execute() method will be wrapped in a RuntimeException

Now with a Java 8 Lambda

public int getUserCount(final String usernameWildcard) {
   return SqlClosure.sqlExecute(connection -> {
      PreparedStatement stmt = connection.prepareStatement("SELECT COUNT(*) FROM users WHERE username LIKE ?"));
      stmt.setString(1, usernameWildcard);
      ResultSet resultSet = stmt.executeQuery();
      return (resultSet.next() ? resultSet.getInt(1) : 0;
   });
}

Note that the lambda automatically closes Statement and ResultSet resources.

As mentioned above, the SqlClosure class is generic, and the signature looks something like this:

public class T SqlClosure<T> {
   public abstract T execute(Connection);
   public T execute();
   public static <V> V sqlExecute(final SqlVarArgsFunction<V> functional, final Object... args);
}

SqlClosure is typically constructed as an anonymous class, and you must provide the implementation of the execute(Connection connection) method. Invoking the execute() method (no parameters) will create a Connection and invoke your overridden method, cleaning up resources in a finally, and returning the value returned by the overridden method. Of course you don't have to execute the closure right away; you could stick it into a queue for later execution, pass it to another method, etc. But typically you'll run execute it right away.

More common still is using Java 8 Lambdas.

Let's look at an example of returning a complex type:

public Set<String> getAllUsernames() {
   return new SqlClosure<Set<String>>() {
      public Set<String> execute(Connection connection) {
         Set<String> usernames = new HashSet<>();
         Statement statement = connection.createStatement();
         ResultSet resultSet = statement.executeQuery("SELECT username FROM users");
         while (resultSet.next()) {
            usernames.add(resultSet.getString("username"));
         }
         return usernames;
      }
   }.execute();
}

And again with Java 8 Lambda

public Set<String> getAllUsernames() {
   return SqlClosure.sqlExecute(connection -> {
      Set<String> usernames = new HashSet<>();
      Statement statement = connection.createStatement();
      ResultSet resultSet = statement.executeQuery("SELECT username FROM users");
      while (resultSet.next()) {
         usernames.add(resultSet.getString("username"));
      }
      return usernames;
   });
}

Even if you use no other features of SansOrm, the SqlClosure class alone can really help to cleanup and simplify your code.

Object Mapping

While the SqlClosure is extremly useful and helps reduce the boilerplate code that you write, we know why you're here: object mapping. Let's jump right in with some examples.

Take this database table:

CREATE TABLE customer (
   customer_id INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY,
   last_name VARCHAR(255),
   first_name VARCHAR(255),
   email VARCHAR(255)
);

Let's imagine a Java class that reflects the table in a straight-forward way, and contains some JPA (javax.persistence) annotations:

Customer:

@Table(name = "customer")
public class Customer {
   @Id
   @GeneratedValue(strategy = GenerationType.IDENTITY)
   @Column(name = "customer_id")
   private int customer_id;

   @Column(name = "last_name")
   private String lastName;

   @Column(name = "first_name")
   private String firstName;

   @Column(name = "email")
   private String emailAddress;

   public Customer() {
      // no arg constuctor declaration is necessary only when other constructors are declared
   }
}

Here we introduce another SansOrm class, OrmElf. What is OrmElf? Well, an 'Elf' is a 'Helper' but with fewer letters to type. Besides, who doesn't like Elves? Let's look at how the OrmElf can help us:

public List<Customer> getAllCustomers() {
   return SqlClosure.sqlExecute( connection -> {
      PreparedStatement pstmt = connection.prepareStatement("SELECT * FROM customer");
      return OrmElf.statementToList(pstmt, Customer.class);
   });
}

The OrmElf will execute the PreparedStatement and using the annotations in the Customer class will construct a List of Customer instances whose values come from the ResultSet. Note that OrmElf will set the properties directly on the object, it does not use getter/setters. Note also that autoClose() was not necessary, the OrmElf will close the statement automatically.

Of course, in addition to querying, the OrmElf can perform basic operations such these (where customer is a Customer):

  • OrmElf.insertObject(connection, customer)
  • OrmElf.updateObject(connection, customer)
  • OrmElf.deleteObject(connection, customer)

Let's make another example, somewhat silly, but showing how queries can be parameterized:

public List<Customer> getCustomersSillyQuery(final int minId, final int maxId, final String like) {
   return SqlClosure.sqlExecute( conn -> {
      PreparedStatement pstmt = conn.prepareStatement(
         "SELECT * FROM customer WHERE (customer_id BETWEEN ? AND ?) AND last_name LIKE ?"));
      return OrmElf.statementToList(pstmt, Customer.class, minId, maxId, like+"%");
   });
}

Well, that's fairly handy. Note the use of varargs. Following the class parameter, zero or more parameters can be passed, and will be used to set query parameters (in order) on the PreparedStatement.

Materializing object instances from rows is so common, there are some further things the 'Elf' can help with. Let's do the same thing as above, but using another helper method.

public List<Customer> getCustomersSillyQuery(final int minId, final int maxId, final String like) {
   return SqlClosure.sqlExecute( connection -> {
      return OrmElf.listFromClause(connection, Customer.class,
                                   "(customer_id BETWEEN ? AND ?) AND last_name LIKE ?",
                                   minId, maxId, like+"%");
   });
}

Now we're cooking with gas! The OrmElf will use the Connection that is passed, along with the annotations on the Customer class to determine which table and columns to SELECT, and use the passed clause as the WHERE portion of the statement (passing 'WHERE' explicitly is also supported), and finally it will use the passed parameters to set the query parameters.

While the SqlClosure is great, and you'll come to wonder how you did without it, for some simple cases like the previous example, it adds a little bit of artiface around what could be even simpler.

Enter SqlClosureElf. Yes, another elf.

public List<Customer> getCustomersSillyQuery(int minId, int maxId, String like) {
   return SqlClosureElf.listFromClause(Customer.class, 
                                       "(customer_id BETWEEN ? AND ?) AND last_name LIKE ?",
                                       minId, maxId, "%"+like+"%");
}

Here the SqlClosureElf is creating the SqlClosure under the covers as well as using the OrmElf to retrieve the list of Customer instances. Like the OrmElf the SqlClosureElf exposes lots of methods for common scenarios, a few are:

  • SqlClosureElf.insertObject(customer)
  • SqlClosureElf.updateObject(customer)
  • SqlClosureElf.deleteObject(customer)

Supported Annotations

Except for the @Table and @MappedSuperclass annotations, which must annotate a class, all other annotations must appear on member variables. Annotations on getter/setter methods are not supported. SansOrm will get/set member variables directly through reflection during read/write operations.

The following annotations are supported:

Annotation Supported Attributes
@Column name, insertable, updatable, table
@Convert converter (AttributeConverter classes only)
@Enumerated value (=EnumType.ORDINAL, EnumType.STRING)
@GeneratedValue strategy (GenerationType.IDENTITY only)
@Id n/a
@JoinColumn name (supports self-join only)
@MappedSuperclass n/a
@Table name
@Transient n/a

By default, SansOrm will lower-case all name and table attribute values, which is fine for DML case-insensitive databases such as PostgreSQL, Derby, Oracle, etc. However, some databases are case-sensitive with respect to identifiers, such as H2. Therefore, SansOrm supports case-sensitive databases through the use of quoted identifiers.

Quoted identifer example:

@Table(name = "\"Customer\"")
class Customer {
   @Column(name = "\"Last_Name\"")
   String lastName;
   ...
}

Automatic Data Type Conversions

Writing

When writing data to JDBC, SansOrm relies on the driver to perform most conversions. SansOrm only calls Statement.setObject() internally, and expects that the driver will properly perform conversions. For example, convert an int or java.lang.Integer into an INTEGER column type.

If the @Convert annotation is present on the field in question, the appropriate user-specified javax.persistence.AttributeConverter will be called.

For fields where the @Enumerated annotation is present, SansOrm will obtain the value to persist by calling ordinal() on the enum instance in the case of EnumType.ORDINAL, and name() on the enum instance in the case of EnumType.STRING.

Reading

When reading data from JDBC, SansOrm relies on the driver to perform most conversions. SansOrm only calls ResultSet.getObject() internally, and expects that the driver will properly perform conversions to Java types. For example , for an INTEGER column type, return a java.lang.Integer from ResultSet.getObject().

However, if the Java object type returned by the driver does not match the type of the mapped member field, SansOrm permits the following automatic conversions:

Driver getObject() Java Type Mapped Member Java type
java.lang.Integer boolean (0 == false, everything else true)
java.math.BigDecimal java.math.BigInteger
java.math.BigDecimal int or java.lang.Integer (via cast)
java.math.BigDecimal long or java.lang.Long (via cast)
java.math.BigDecimal double or java.lang.Double (via cast)
java.util.UUID String
java.sql.Clob String

If the @Convert annotation is present on the field in question, the appropriate user-specified javax.persistence.AttributeConverter will be called.

For fields where the @Enumerated annotation is present, SansOrm will map java.lang.Integer values from the driver to the correct Enum value in the case of EnumType.ORDINAL, and will map java.lang.String values from the driver to the correct Enum value in the case of EnumType.STRING.

Finally, SansOrm has specific support for the PostgreSQL PGobject and CITEXT data types. CITEXT column values are converted to java.lang.String. PGobject "unknown type" column values have their getValue() method called, and the result is attempted to be set via reflection onto the mapped member field.

More Advanced

Just page as provided just a taste, so go on over to the Advanced Usage page to go deep.

sansorm's People

Contributors

brettwooldridge avatar dnebing avatar elmatadorinho avatar h-thurow avatar lfbayer avatar nicolaisotta avatar seeekr avatar sergiorussia 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  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

sansorm's Issues

Timestamps lose microseconds in OrmBase.mapSqlType

java.sql.Timestamp is instance of java.util.Date.
The code below (in OrmBase.mapSqlType) truncates Timestamp precision to milliseconds

  case Types.TIMESTAMP:
     if (object instanceof java.util.Date) {
        return new Timestamp(((java.util.Date) object).getTime());  // << microseconds are lost if object is already a Timestamp
     }
     break;

Simple solution:

  case Types.TIMESTAMP:
     if (object instanceof Timestamp) {
        return object;
     }
     if (object instanceof java.util.Date) {
        return new Timestamp(((java.util.Date) object).getTime());
     }
     break;

JOIN QUERY need a new entity to receive resultSet

Hi writer
Can your project resolve my question?
JOIN QUERY always produce a new resultSet, and i always must new a entity that receive it. that's let me down, i dont know if your project can handle this.

Table annotation shouldn't be mandatory

Right now, for CRUD operations, database table name is evaluated in this method:

/**
* Get the table name specified by the {@link Table} annotation.
*/
private void extractClassTableName() {
final Table tableAnnotation = clazz.getAnnotation(Table.class);
if (tableAnnotation != null) {
final String tableName = tableAnnotation.name();
this.tableName = tableName.isEmpty()
? clazz.getSimpleName() // as per documentation, empty name in Table "defaults to the entity name"
: tableName;
}
}

If the class does not have the @Table annotation, tableName is null and this could lead to various type of confusing exception from the jdbc drivers, such as: com.microsoft.sqlserver.jdbc.SQLServerException: Incorrect syntax near the keyword 'null'.

Is the a reason why this method does not default to clazz.getSimpleName() when there is no @Table annotation?

Benchmark

Brett, I really liked SansORM. Wondering if you have any performance benchmark comparing this against just plain old JDBC?

Possible to add MERGE support?

It seems pretty simple to add H2's merge function to OrmWriter, but it's a right pain to add it externally.

Is there a reason why it's absent at the minute?

Question concering closing connections in SqlClosure

This is probably just a stupid question, but here we go:

At the end of the public final T execute() method of SqlClosure the finally clause within the finally (line 227) applies for all connections, regardless of the value of txOwner.
I.e. a connection is closed, even when an external TransactionManager is present.
Is that intendet?

Fix API Inconsistencies

@sergiorussia I have a favor to ask. I want you to break our API. 😄

As SansORM has evolved over time, some minor inconsistencies have crept in; due to expendiency as well as lack of clarity about the separation of concerns (I'm guilty on both counts).

Because master already contains breaking changes (the removal of deprecated methods), the next release needs to go out as v3.x.x. So, now is the perfect time to make unnecessary work for myself ... er ... the perfect time to move things around.

Background

As originally conceived, OrmElf was to contain methods that deal in the currency of "objects" (/classes), but know nothing of where Connection, Statement, or ResultSet objects come from. For example:

public static <T> T resultSetToObject(ResultSet resultSet, T target) throws SQLException

public static <T> List<T> listFromClause(Connection connection, Class<T> clazz, String clause, Object... args) throws SQLException
...

Whereas SqlClosureElf knows how to acquire connections (using SqlClosure), and in addition to "object" convenience methods, might also contain pure SQL convenience methods.

Inconsistencies

Unfortunately, some pure SQL functionality has crept into OrmElf, and I would like to see it moved to SqlClosureElf. The ones I have identified are these:

public static String getInClausePlaceholders(final String...items)

public static ResultSet executeQuery(Connection connection, String sql, Object... args) throws SQLException

public static int executeUpdate(Connection connection, String sql, Object... args) throws SQLException

public static Number numberFromSql(Connection connection, String sql, Object... args) throws SQLException

Do you feel like refactoring those?

Problematic license in Introspector.java

Hello,

I was planning on using this library but then stumbled upon:

src/main/java/com/zaxxer/sansorm/internal/Introspector.java which has this copyright header:

/*
 * The contents of this file are proprietary and may not be
 * modified or distributed in source or compiled form without
 * express written permission from the copyright holders.
 *
 * Copyright 2008-2011, DancerNetworks
 * All rights reserved.
 */

Everything else seems to be Apache 2.

Inconsistent behaviour for Id fields that also have an AttributeConverter

For entity classes whose Id fields also have a Converter attribute, the behaviour of SansOrm is buggy (or at least inconsitent):

  • When persisting an entity, e.g. via OrmElf->OrmWriter.writeObject(), the actual id value written to the database is determined via Introspected.get(target, FieldColumnInfo), which, as expected, applies the AttributeConverter.toDatabaseColumn() (or Enum conversion).
  • When requesting that entity using OrmElf->OrmReader.getObjectById(), using (ofcourse) the "java-side" id value of the entity, it won't be found, since getObjectById() just uses the given value as bind parameter, without applying the appropriate toDatabaseColumn() converter first.

For OrmReader.getObjectById() specific, a quick and dirty fix could be:

   ...

   public static <T> T objectById(final Connection connection, final Class<T> clazz, final Object... args) throws SQLException
   {
      String where = getWhereIdClause(Introspector.getIntrospected(clazz), args);
      return objectFromClause(connection, clazz, where, args);
   }

   ...

   private static String getWhereIdClause(Introspected introspected, final Object... args) {
      final StringBuilder where = new StringBuilder();
      List<FieldColumnInfo> idFcInfos = introspected.getIdFcInfos();
      int i = 0;
      for (FieldColumnInfo fcInfo : idFcInfos) {
         where.append(fcInfo.getColumnName()).append("=? AND ");
         if (i < args.length) {
            // Maybe need to convert the argument, using same logic as Introspected.get()
            // Note: modifying the given args array!
            if (fcInfo.getConverter() != null) {
               args[i] = fcInfo.getConverter().convertToDatabaseColumn(args[i]);
            } else if (fcInfo.enumConstants != null && args[i] != null) {
               args[i] = (fcInfo.enumType == EnumType.ORDINAL ? ((Enum<?>) args[i]).ordinal() : ((Enum<?>) args[i]).name());
            }
         }
         i++;
      }
      // the where clause can be length of zero if we are loading an object that is presumed to
      // be the only row in the table and therefore has no id.
      if (where.length() > 0) {
      ...

However:

  • The same issue also pops up in other places, for example in OrmWriter (setParamsExecute, deleteObject, updateObject), or maybe Introspected.getActualIds, so a more structural solution seems appropriate.
  • It would more or less duplicate existing conversion code currently in Introspected.get; maybe it's better to create a conversion function in that class.

Any ideas on this?

Library not available in maven central repos

Hello.
Thanks for your impressive work.

I would like to try your library.
Mostly its ability to map ResultSet to object in conjunction with gorgeus SqlObject API of another SQL library, JDBI (unfortunately, it has very poor object mapping capabilities).

But unfortunately I cannot find it in any central repos. Even beta or alpha version. Please publish your library in maven repos, it will make installation process easier and will certainly boost library's popularity.

P.S. Sorry for my english.

JPA dependency and automatic Bean mapping

I am wondering if there is any way JPA dependency can be avoided and instead resultset can be directly mapped to POJO just like Spring JDBC template. In my opinion it'll be one less dependency to worry about.

BigDecimal -> Double conversion

Hello, is there any reason why BigDecimal is not converted to Double if fieldType == Double.class?

else if (columnType == BigDecimal.class) {
if (fieldType == BigInteger.class) {
columnValue = ((BigDecimal) columnValue).toBigInteger();
}
else if (fieldType == Integer.class) {
columnValue = (int) ((BigDecimal) columnValue).longValue();
}
else if (fieldType == Long.class) {
columnValue = ((BigDecimal) columnValue).longValue();
}
}

OSGi Support?

I notice that 3.7 has appropriate OSGi bundle headers, but with the heavy use of static members and thread locals, I'm wondering if the library has been tested in an OSGi container and what the circumstances of the testing was?

OrmElf NPE when Class doesn't have a table column

When trying to map a query to a "POJO", if the query returns a column non existent in the POJO classe, we get a NullPointerException.
Could / Should this be treated and those column ignored in the mapping?

Problem with upper case column names

We use a sybase database with uppercase table and column names.

In Introspected#processColumnAnnotation the FieldColumnInfo.columnName is lower cased from the @column annotation. This leads to errors with the SQL statements generated by SqlClosureElf. (changing the behavior of sybase requires a global modification of sorting on the sybase server and is not an option.)

Is the toLowerCase() required for column names or can this be changed - in gereral or with a configurable default behavior?

Besides getting the column name from field name is not consistent.
If the field has a @column annotation without name attribute, the field name is accepted as is. (line 475)
If the field has no annotation the field name will be lower cased. (line 501)

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.