brettwooldridge / SansOrm

A "No-ORM" sane SQL ←→ Java object mapping library

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Fix API Inconsistencies

brettwooldridge opened this issue Β· comments

@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?

@brettwooldridge sure, why not. we can also:

  • add OrmElf::list as a simple case for listFromClause(..., clause="")
  • drop deprecated execute
  • expose few more private helper methods (can't remember all of them, but remember that there are useful ones)
  • drop JTA requirement (see SQLite-based tests, this is the simplified solution from our project, but it would be better be entirely optional) and provide optional dependencies for JPA annotation
  • introduce a single point of SansOrm setup - i've spent some time figuring out what statics i should call to make it work. i suggest something like a single class with several methods like SansOrm::configure(DataSource ds), configure(DS, TransactionManager tm, UserTransaction ut), which in turn will simply delegate to SqlClosure::setDS and TrElf::setTM and TrElf::setUT

but before that i ask you to publish the final(?) build in 2.x series, i want it now πŸ˜„

@sergiorussia v2.8 published to Sonatype. Should be available immediately from that repo, or in a few hours from Central once it syncs.

@brettwooldridge thanks. i think i could start refactoring pretty soon. questions:

  • what do you think about suggestions mentioned above?
  • how we can get this organized? same regular flow as for previous PRs or what?

btw i suggest you to disable merges in this repo, leaving only squash (see "Merge button" section in repo Settings) - merges makes repo history a mess unless all work in forks are made in branches

@sergiorussia I have comments, but they will have to wait until tomorrow ... bedtime here in Tokyo and I need to send my daughter off to bed. 😴

btw, you said "master already contains breaking changes" - it's not, deprecated method is still there, it is just not used anymore. just sayin

Again some background...

Background

Dating back to our use of Hibernate, we used JTA (Bitronix). Our use of Bitronix continued after our switch to SansOrm. Bitronix includes it's own connection pool implementation.

In 2015, we switched to HikariCP for pooling, and removed the dependency on Bitronix. However, the behavior that JTA provides was still desirable. To that end, we created a "faux" (fake) JTA implementation.

Specifically, what does JTA offer that is desirable? Primarily, thread-associated Connections.

For example:

void methodOne() {
   SqlClosureElf.sqlExecute(conn -> {
      // SQL / SansOrm interactions
      ...
      methodTwo();
   });
}

void methodTwo() {
   SqlClosureElf.sqlExecute(conn -> {
      // SQL / SansOrm interactions
   });
}

In this case, the presence of a JTA ensures that the SqlClosure in methodTwo() is given the same Connection instance as that given to methodOne().

This ensures that methodTwo() runs within the same transaction as methodOne(). No matter how deep the calls go, for example, if methodTwo() calls methodThree(), that method too will be involved in the same transaction.

The end result is not only the transaction encapsulation, but also a signifcant reduction of Connection instances required from the pool. And finally, and further, it serves to eliminate deadlocks in the database that a single-thread alone can get entangled in.

On this last point, assuming no JTA, if methodOne() acquires a Connection and performs SQL DELETES, and methodTwo() acquires a different Connection (while the transaction from methodOne() is still open), then a SQL SELECT could deadlock. The opposite is also true. If methodOne() initiates a SQL SELECT with a cursor that locks a table, then methodTwo() executing a DELETE (or INSERT) on a separate Connection (transaction), then a deadlock will occur.

A JTA avoids all of these issues.

What I propose is that our currently private stub-JTA be folded into SansOrm as the default JTA, which can naturally be overridden with a real JTA like Bitronix or Atomicos without change to SansOrm.

yeah, it would be great. at least to make nested calls respect previously opened tx. the reason i suggested to make JTA optional is for cases like SQLite, when you can only have pool with max size == 1 (if you do writes, and you 99% do) - in this case Hikari with max pool size of 1 will resolve multithreading issues like deadlocks etc. but in general yeah, default JTA impl will be a big plus.

i'd suggest to make a branch for 3.x, then you add your JTA impl there, then i refactor. thoughts?

@sergiorussia Sounds good. In addition to that, I am willing to make you a Contributor to the project, if you are interested. As a courtesy, initially, I would still like pull requests. After review and sign-off you will be able to commit (squash) the change directly.

This is not out of distrust, your work has been excellent so far, but more out of protection. As you saw with the last change regarding the zero-based generated IDs, we (my company) may have made assumptions about behavior that are not apparent in the code and we just want to avoid breakage.

@sergiorussia Branch v3 has been created.

i'm used to make changes via PRs, that's ok. i'll make changes in a separate branches, then PRs to v3 etc.

@brettwooldridge any ETA for that JTA impl to get into v3?

PS: my migrating from Hibernate we finally won that connection leak (issue 923 in HikariCP) πŸ‘

Already committed a few days ago.

@brettwooldridge shouldn't "Closes" comment close the issue? or the only way to close an issue is manually?

@sergiorussia It should. Maybe it is confused by multiple closes?

dunno. let's close it by ourselves then

ah, the thing is that v3 is NOT the default branch (which is master)

@brettwooldridge , i've looked at things that SansOrm API lacks for us - did not find any outstanding. we could maybe add more features etc but there's no need for them yet. so i suggest to merge v3 into master and release the build.

@sergiorussia Luckily, the number of user's is not so great that a mis-step would cause any real pain. 😁 Additionally, the v3.x revision bump implies breaking changes. I'll cut the release.

@sergiorussia Published to the sonatype repository. Available now, there, or from the central repository in a couple of hours, after replication.

@brettwooldridge um, something gone wrong. check out http://repo1.maven.org/maven2/com/zaxxer/sansorm/3.0/, lib jar misses "transaction" package, while sources jar has it

@sergiorussia v3.1 has been published.

@brettwooldridge works, thanks. btw, i were thinking - do we really need TransactionManager or UserTransaction would be enough? the only things TM is used for are TrElf.suspend and TrElf.resume - UserTx covers the rest. previously i had write a very simple custom UserTx impl with nesting support and thread-local storage, which is did roughly the same as TxXXX classes.

the question arose when i tried to compare performance for my in-house solution with yours - and Sans API forces me to implement TransationManager even i would never use suspend/resume methods. do you use those at your projects?

just found that those methods are implemented by raising SystemException in TxTrMan :)

also found that tx rollback does not rollbacks anything in case if any non-SqlException is thrown - everything is committed. this is because SqlClosure::execute handles only SqlException. see PR #20