Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add the ability to change a table name to AliasableSqlTable #572

Merged
merged 13 commits into from Jan 7, 2023
5 changes: 5 additions & 0 deletions CHANGELOG.md
Expand Up @@ -46,6 +46,11 @@ The pull request for this change is ([#550](https://github.com/mybatis/mybatis-d
3. Refactored the common insert mapper support for MyBatis3 by adding a CommonGeneralInsertMapper that can be used
without a class that matches the table row. It includes methods for general insert and insert select.
([#570](https://github.com/mybatis/mybatis-dynamic-sql/pull/570))
4. Added the ability to change a table name on AliasableSqlTable - this creates a new instance of the object with a new
name. This is useful in sharded databases where the name of the table is calculated based on some sharding
algorithm. Also deprecated the constructors on SqlTable that accept Suppliers for table name - this creates an
effectively mutable object and goes against the principles of immutability that we strive for in the library.
([#572](https://github.com/mybatis/mybatis-dynamic-sql/pull/572))



Expand Down
22 changes: 17 additions & 5 deletions src/main/java/org/mybatis/dynamic/sql/AliasableSqlTable.java
Expand Up @@ -29,14 +29,26 @@ protected AliasableSqlTable(String tableName, Supplier<T> constructor) {
this.constructor = Objects.requireNonNull(constructor);
}

protected AliasableSqlTable(Supplier<String> tableNameSupplier, Supplier<T> constructor) {
super(tableNameSupplier);
this.constructor = Objects.requireNonNull(constructor);
}

public T withAlias(String alias) {
T newTable = constructor.get();
((AliasableSqlTable<T>) newTable).tableAlias = alias;
newTable.nameSupplier = nameSupplier;
return newTable;
}

/**
* Returns a new instance of this table with the specified name. All column instances are recreated.
* This is useful for sharding where the table name may change at runtime based on some sharding algorithm,
* but all other table attributes are the same.
*
* @param name new name for the table
* @return a new AliasableSqlTable with the specified name, all other table attributes are copied
*/
public T withName(String name) {
Objects.requireNonNull(name);
T newTable = constructor.get();
((AliasableSqlTable<T>) newTable).tableAlias = tableAlias;
newTable.nameSupplier = () -> name;
return newTable;
}

Expand Down
26 changes: 25 additions & 1 deletion src/main/java/org/mybatis/dynamic/sql/SqlTable.java
Expand Up @@ -24,24 +24,48 @@

public class SqlTable implements TableExpression {

private final Supplier<String> nameSupplier;
protected Supplier<String> nameSupplier;

protected SqlTable(String tableName) {
Objects.requireNonNull(tableName);

this.nameSupplier = () -> tableName;
}

/**
* Creates an SqlTable whose name can be changed at runtime.
*
* @param tableNameSupplier table name supplier
* @deprecated please use {@link AliasableSqlTable} if you need to change the table name at runtime
*/
@Deprecated
protected SqlTable(Supplier<String> tableNameSupplier) {
Objects.requireNonNull(tableNameSupplier);

this.nameSupplier = tableNameSupplier;
}

/**
* Creates an SqlTable whose name can be changed at runtime.
*
* @param schemaSupplier schema supplier
* @param tableName table name
* @deprecated please use {@link AliasableSqlTable} if you need to change the table name at runtime
*/
@Deprecated
protected SqlTable(Supplier<Optional<String>> schemaSupplier, String tableName) {
this(Optional::empty, schemaSupplier, tableName);
}

/**
* Creates an SqlTable whose name can be changed at runtime.
*
* @param catalogSupplier catalog supplier
* @param schemaSupplier schema supplier
* @param tableName table name
* @deprecated please use {@link AliasableSqlTable} if you need to change the table name at runtime
*/
@Deprecated
protected SqlTable(Supplier<Optional<String>> catalogSupplier, Supplier<Optional<String>> schemaSupplier,
String tableName) {
Objects.requireNonNull(catalogSupplier);
Expand Down
Expand Up @@ -561,7 +561,8 @@ public QueryExpressionHavingBuilder having(SqlCriterion initialCriterion, AndOrC
return having(initialCriterion, Arrays.asList(subCriteria));
}

public QueryExpressionHavingBuilder having(SqlCriterion initialCriterion, List<AndOrCriteriaGroup> subCriteria) {
public QueryExpressionHavingBuilder having(SqlCriterion initialCriterion,
List<AndOrCriteriaGroup> subCriteria) {
return QueryExpressionDSL.this.having(initialCriterion, subCriteria);
}
}
Expand Down Expand Up @@ -599,7 +600,8 @@ public FromGatherer<R> selectDistinct(List<BasicColumn> selectList) {
}
}

public class QueryExpressionHavingBuilder extends AbstractBooleanExpressionDSL<QueryExpressionHavingBuilder> implements Buildable<R> {
public class QueryExpressionHavingBuilder extends AbstractBooleanExpressionDSL<QueryExpressionHavingBuilder>
implements Buildable<R> {

public QueryExpressionHavingBuilder(SqlCriterion initialCriterion) {
setInitialCriterion(initialCriterion);
Expand Down
122 changes: 23 additions & 99 deletions src/site/markdown/docs/databaseObjects.md
Expand Up @@ -4,14 +4,16 @@ MyBatis Dynamic SQL works with Java objects that represent relational tables or
## Table or View Representation

The class `org.mybatis.dynamic.sql.SqlTable` is used to represent a table or view in a database. An `SqlTable` holds a
name, and a collection of `SqlColumn` objects that represent the columns in a table or view.
name, and a collection of `SqlColumn` objects that represent the columns in a table or view. A subclass of `SqlTable` -
`AliasableSqlTable` should be used in cases where you want to specify a table alias that should be used in all cases,
or if you need to change the table name at runtime.

A table or view name in SQL has three parts:

1. The catalog - which is optional and is rarely used outside of Microsoft SQL Server. If unspecified the default
catalog will be used - and many databases only have one catalog
1. The schema - which is optional but is very often specified. If unspecified, the default schema will be used
1. The table name - which is required
2. The schema - which is optional but is very often specified. If unspecified, the default schema will be used
3. The table name - which is required

Typical examples of names are as follows:

Expand All @@ -21,126 +23,48 @@ Typical examples of names are as follows:
- `"bar"` - a name with just a table name (bar). This will access a table or view in the default catalog and schema for
a connection

In MyBatis Dynamic SQL, the table or view name can be specified in different ways:
In MyBatis Dynamic SQL, the full name of the table should be supplied on the constructor of the table object.
If a table name needs to change at runtime (say for sharding support), then use the `withName` method on
`AliasableSqlTable` to create an instance with the new name.

1. The name can be a constant String
1. The name can be calculated at runtime based on a catalog and/or schema supplier functions and a constant table name
1. The name can be calculated at runtime with a name supplier function
We recommend using the base class `AliasableSqlTable` in all cases as it provides the most flexibility. The
`SqlTable` class remains in the library for compatibility with older code only.

### Constant Names

Constant names are used when you use the `SqlTable` constructor with a single String argument. For example:
For example:

```java
public class MyTable extends SqlTable {
import org.mybatis.dynamic.sql.AliasableSqlTable;

public class MyTable extends AliasableSqlTable<MyTable> {
public MyTable() {
super("MyTable");
super("MyTable", MyTable::new);
}
}
```

Or

```java
public class MyTable extends SqlTable {
public class MyTable extends AliasableSqlTable<MyTable> {
public MyTable() {
super("MySchema.MyTable");
}
}
```

### Dynamic Catalog and/or Schema Names
MyBatis Dynamic SQL allows you to dynamically specify a catalog and/or schema. This is useful for applications where
the schema may change for different users or environments, or if you are using different schemas to shard the database.
Dynamic names are used when you use a `SqlTable` constructor that accepts one or more `java.util.function.Supplier`
arguments.

For example, suppose you wanted to change the schema based on the value of a system property. You could write a class
like this:

```java
public class SchemaSupplier {
public static final String schema_property = "schemaToUse";

public static Optional<String> schemaPropertyReader() {
return Optional.ofNullable(System.getProperty(schema_property));
super("MySchema.MyTable", MyTable::new);
}
}
```

This class has a static method `schemaPropertyReader` that will return an `Optional<String>` containing the value of a
system property. You could then reference this method in the constructor of the `SqlTable` like this:

```java
public static final class User extends SqlTable {
public User() {
super(SchemaSupplier::schemaPropertyReader, "User");
}
}
```

Whenever the table is referenced for rendering SQL, the name will be calculated based on the current value of the
system property.

There are two constructors that can be used for dynamic names:

1. A constructor that accepts `Supplier<Optional<String>>` for the schema, and `String` for the name. This constructor
assumes that the catalog is always empty or not used
1. A constructor that accepts `Supplier<Optional<String>>` for the catalog, `Supplier<Optional<String>>` for the schema,
and `String` for the name

If you are using Microsoft SQL Server and want to use a dynamic catalog name and ignore the schema, then you should use
the second constructor like this:
You can change a table name:

```java
public static final class User extends SqlTable {
public User() {
super(CatalogSupplier::catalogPropertyReader, Optional::empty, "User");
}
}
```

The following table shows how the name is calculated in all combinations of suppliers:

Catalog Supplier Value | Schema Supplier Value | Name | Fully Qualified Name
---|---|---|---
"MyCatalog" | "MySchema" | "MyTable" | "MyCatalog.MySchema.MyTable"
&lt;empty&gt; | "MySchema" | "MyTable" | "MySchema.MyTable"
"MyCatalog" | &lt;empty&gt; | "MyTable" | "MyCatalog..MyTable"
&lt;empty&gt; | &lt;empty&gt; | "MyTable" | "MyTable"


### Fully Dynamic Names
MyBatis Dynamic SQL allows you to dynamically specify a full table name. This is useful for applications where the
database is sharded with different tables representing different shards of the whole. Dynamic names are used when you
use a `SqlTable` constructor that accepts a single `java.util.function.Supplier` argument.

For example, suppose you wanted to change the name based on the value of a system property. You could write a class
like this:

```java
public class NameSupplier {
public static final String name_property = "nameToUse";

public static String namePropertyReader() {
return System.getProperty(name_property);
public class MyTable extends AliasableSqlTable<MyTable> {
public MyTable() {
super("Schema1.MyTable", MyTable::new);
}
}
```

This class has a static method `namePropertyReader` that will return an `String` containing the value of a system
property. You could then reference this method in the constructor of the `SqlTable` like this:

```java
public static final class User extends SqlTable {
public User() {
super(NameSupplier::namePropertyReader);
}
}
MyTable schema1Table = new MyTable();
MyTable schema2Table = schema1Table.withName("Schema2.MyTable");
```

Whenever the table is referenced for rendering SQL, the name will be calculated based on the current value of the system property.

## Aliased Tables

In join queries, it is usually a good practice to specify table aliases. The `select` statement includes
Expand Down
2 changes: 1 addition & 1 deletion src/test/java/examples/joins/UserDynamicSQLSupport.java
Expand Up @@ -32,7 +32,7 @@ public static final class User extends AliasableSqlTable<User> {
public final SqlColumn<Integer> parentId = column("parent_id", JDBCType.INTEGER);

public User() {
super(() -> "User", User::new);
super("User", User::new);
}
}
}
23 changes: 23 additions & 0 deletions src/test/java/examples/sharding/ShardedMapper.java
@@ -0,0 +1,23 @@
/*
* Copyright 2016-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package examples.sharding;

import org.mybatis.dynamic.sql.util.mybatis3.CommonCountMapper;
import org.mybatis.dynamic.sql.util.mybatis3.CommonGeneralInsertMapper;
import org.mybatis.dynamic.sql.util.mybatis3.CommonSelectMapper;

public interface ShardedMapper extends CommonCountMapper, CommonGeneralInsertMapper, CommonSelectMapper {
}