Skip to content

Commit d67140c

Browse files
committed
Add AliasableSQLColumn
1 parent f03b6b6 commit d67140c

File tree

10 files changed

+319
-10
lines changed

10 files changed

+319
-10
lines changed

CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,16 @@
22

33
This log will detail notable changes to MyBatis Dynamic SQL. Full details are available on the GitHub milestone pages.
44

5+
## Release 1.3.1 - Unreleased
6+
7+
GitHub milestone: [https://github.com/mybatis/mybatis-dynamic-sql/issues?q=milestone%3A1.3.1+](https://github.com/mybatis/mybatis-dynamic-sql/issues?q=milestone%3A1.3.1+)
8+
9+
### Added
10+
11+
- Added the ability to specify a JavaType associated with a column. The JavaType will be rendered properly for MyBatis ([#386](https://github.com/mybatis/mybatis-dynamic-sql/pull/386))
12+
- Added a few missing groupBy and orderBy methods on the `select` statement ([#409](https://github.com/mybatis/mybatis-dynamic-sql/pull/409))
13+
- Added a new extension of SqlTable that supports setting a table alias directly within the table definition
14+
515
## Release 1.3.0 - May 6, 2021
616

717
GitHub milestone: [https://github.com/mybatis/mybatis-dynamic-sql/issues?q=milestone%3A1.3.0+](https://github.com/mybatis/mybatis-dynamic-sql/issues?q=milestone%3A1.3.0+)
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/*
2+
* Copyright 2016-2021 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.mybatis.dynamic.sql;
17+
18+
import java.util.Objects;
19+
import java.util.Optional;
20+
import java.util.function.Supplier;
21+
22+
public abstract class AliasableSqlTable<T extends AliasableSqlTable<T>> extends SqlTable {
23+
24+
private String tableAlias;
25+
private final Supplier<T> constructor;
26+
27+
protected AliasableSqlTable(String tableName, Supplier<T> constructor) {
28+
super(tableName);
29+
this.constructor = Objects.requireNonNull(constructor);
30+
}
31+
32+
protected AliasableSqlTable(Supplier<String> tableNameSupplier, Supplier<T> constructor) {
33+
super(tableNameSupplier);
34+
this.constructor = Objects.requireNonNull(constructor);
35+
}
36+
37+
public T withAlias(String alias) {
38+
T newTable = constructor.get();
39+
((AliasableSqlTable<T>) newTable).tableAlias = alias;
40+
return newTable;
41+
}
42+
43+
@Override
44+
public Optional<String> tableAlias() {
45+
return Optional.ofNullable(tableAlias);
46+
}
47+
}

src/main/java/org/mybatis/dynamic/sql/SqlTable.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2016-2020 the original author or authors.
2+
* Copyright 2016-2021 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -108,6 +108,10 @@ public <R> R accept(TableExpressionVisitor<R> visitor) {
108108
return visitor.visit(this);
109109
}
110110

111+
public Optional<String> tableAlias() {
112+
return Optional.empty();
113+
}
114+
111115
public static SqlTable of(String name) {
112116
return new SqlTable(name);
113117
}

src/main/java/org/mybatis/dynamic/sql/render/TableAliasCalculator.java

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2016-2020 the original author or authors.
2+
* Copyright 2016-2021 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -32,11 +32,20 @@ protected TableAliasCalculator(Map<SqlTable, String> aliases) {
3232
}
3333

3434
public Optional<String> aliasForColumn(SqlTable table) {
35-
return Optional.ofNullable(aliases.get(table));
35+
return explicitAliasOrTableAlias(table);
3636
}
3737

3838
public Optional<String> aliasForTable(SqlTable table) {
39-
return Optional.ofNullable(aliases.get(table));
39+
return explicitAliasOrTableAlias(table);
40+
}
41+
42+
private Optional<String> explicitAliasOrTableAlias(SqlTable table) {
43+
String alias = aliases.get(table);
44+
if (alias == null) {
45+
return table.tableAlias();
46+
} else {
47+
return Optional.of(alias);
48+
}
4049
}
4150

4251
public static TableAliasCalculator of(SqlTable table, String alias) {

src/site/markdown/docs/databaseObjects.md

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,88 @@ public static final class User extends SqlTable {
141141

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

144+
## Aliased Tables
145+
146+
In join queries, it is usually a good practice to specify table aliases. The `select` statement includes
147+
support for specifying table aliases in each query in a way that looks like natural SQL. For example:
148+
149+
```java
150+
SelectStatementProvider selectStatement = select(orderMaster.orderId, orderDate, orderLine.lineNumber, itemMaster.description, orderLine.quantity)
151+
.from(orderMaster, "om")
152+
.join(orderLine, "ol").on(orderMaster.orderId, equalTo(orderLine.orderId))
153+
.join(itemMaster, "im").on(orderLine.itemId, equalTo(itemMaster.itemId))
154+
.where(orderMaster.orderId, isEqualTo(2))
155+
.build()
156+
.render(RenderingStrategies.MYBATIS3);
157+
```
158+
159+
In a query like this, the library will automatically append the table alias to the column name when the query is rendered.
160+
Internally, the alias for a column is determined by looking up the associated table in a HashMap maintained within the
161+
query model. If you do not specify a table alias, the library will automatically append the table name in join queries.
162+
163+
Unfortunately, this strategy fails for self-joins. It can also get confusing when there are sub-queries. Imagine a
164+
query like this:
165+
166+
```java
167+
SelectStatementProvider selectStatement = select(user.userId, user.userName, user.parentId)
168+
.from(user, "u1")
169+
.join(user, "u2").on(user.userId, equalTo(user.parentId))
170+
.where(user.userId, isEqualTo(4))
171+
.build()
172+
.render(RenderingStrategies.MYBATIS3);
173+
```
174+
175+
In this query it is not clear which instance of the `user` table is used for each column, and there will only be entry in the
176+
HashMap for the `user` table - so only one of the aliases specified in the select statement will be in effect.
177+
There are two ways to deal with this problem.
178+
179+
The first is to simply create another instance of the User SqlTable object. With this method it is very clear which column
180+
belongs to which instance of the table and the library can easily calculate aliases:
181+
182+
```java
183+
User user1 = new User();
184+
User user2 = new User();
185+
SelectStatementProvider selectStatement = select(user1.userId, user1.userName, user1.parentId)
186+
.from(user1, "u1")
187+
.join(user2, "u2").on(user1.userId, equalTo(user2.parentId))
188+
.where(user2.userId, isEqualTo(4))
189+
.build()
190+
.render(RenderingStrategies.MYBATIS3);
191+
```
192+
193+
Starting with version 1.3.1, there is new method where the alias can be specified in the table object itself. This allows
194+
you to move the aliases out of the `select` statement.
195+
196+
```java
197+
User user1 = user.withAlias("u1");
198+
User user2 = user.withAlias("u2");
199+
200+
SelectStatementProvider selectStatement = select(user1.userId, user1.userName, user1.parentId)
201+
.from(user1)
202+
.join(user2).on(user1.userId, equalTo(user2.parentId))
203+
.where(user2.userId, isEqualTo(4))
204+
.build()
205+
.render(RenderingStrategies.MYBATIS3);
206+
```
207+
208+
To enable this support, your table objects should extend `org.mybatis.dynamic.sql.AliasableSqlTable` rather than
209+
`org.mybatis.dynamic.sql.SqlTable` as follows:
210+
211+
```java
212+
public static final class User extends AliasableSqlTable<User> {
213+
public final SqlColumn<Integer> userId = column("user_id", JDBCType.INTEGER);
214+
public final SqlColumn<String> userName = column("user_name", JDBCType.VARCHAR);
215+
public final SqlColumn<Integer> parentId = column("parent_id", JDBCType.INTEGER);
216+
217+
public User() {
218+
super("User", User::new);
219+
}
220+
}
221+
```
222+
223+
If you use an aliased table object, and also specify an alias in the `select` statement, the alias from the `select`
224+
statement will override the alias in the table object.
225+
144226
## Column Representation
145227

146228
The class `org.mybatis.dynamic.sql.SqlColumn` is used to represent a column in a table or view. An `SqlColumn` is always

src/site/markdown/docs/select.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ The library supports the generation of UNION and UNION ALL queries. For example:
8585

8686
Any number of SELECT statements can be added to a UNION query. Only one ORDER BY phrase is allowed.
8787

88-
## Annotated Mapper for Select Statements
88+
## MyBatis Mapper for Select Statements
8989

9090
The SelectStatementProvider object can be used as a parameter to a MyBatis mapper method directly. If you
9191
are using an annotated mapper, the select method should look like this (note that we recommend coding a "selectMany" and a "selectOne" method with a shared result mapping):

src/test/java/examples/joins/JoinMapperTest.java

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -958,6 +958,68 @@ void testSelfWithDuplicateAlias() {
958958
).withMessage("Table \"User\" with requested alias \"u2\" is already aliased in this query with alias \"u1\". Attempting to re-alias a table in the same query is not supported.");
959959
}
960960

961+
@Test
962+
void testSelfWithNewAlias() {
963+
try (SqlSession session = sqlSessionFactory.openSession()) {
964+
JoinMapper mapper = session.getMapper(JoinMapper.class);
965+
966+
// create second table instance for self-join
967+
UserDynamicSQLSupport.User user2 = user.withAlias("u2");
968+
969+
// get Bamm Bamm's parent - should be Barney
970+
SelectStatementProvider selectStatement = select(user.userId, user.userName, user.parentId)
971+
.from(user)
972+
.join(user2).on(user.userId, equalTo(user2.parentId))
973+
.where(user2.userId, isEqualTo(4))
974+
.build()
975+
.render(RenderingStrategies.MYBATIS3);
976+
977+
String expectedStatement = "select User.user_id, User.user_name, User.parent_id"
978+
+ " from User join User u2 on User.user_id = u2.parent_id"
979+
+ " where u2.user_id = #{parameters.p1,jdbcType=INTEGER}";
980+
assertThat(selectStatement.getSelectStatement()).isEqualTo(expectedStatement);
981+
982+
List<User> rows = mapper.selectUsers(selectStatement);
983+
984+
assertThat(rows).hasSize(1);
985+
User row = rows.get(0);
986+
assertThat(row.getUserId()).isEqualTo(2);
987+
assertThat(row.getUserName()).isEqualTo("Barney");
988+
assertThat(row.getParentId()).isNull();
989+
}
990+
}
991+
992+
@Test
993+
void testSelfWithNewAliasAndOverride() {
994+
try (SqlSession session = sqlSessionFactory.openSession()) {
995+
JoinMapper mapper = session.getMapper(JoinMapper.class);
996+
997+
// create second table instance for self-join
998+
UserDynamicSQLSupport.User user2 = user.withAlias("other_user");
999+
1000+
// get Bamm Bamm's parent - should be Barney
1001+
SelectStatementProvider selectStatement = select(user.userId, user.userName, user.parentId)
1002+
.from(user, "u1")
1003+
.join(user2, "u2").on(user.userId, equalTo(user2.parentId))
1004+
.where(user2.userId, isEqualTo(4))
1005+
.build()
1006+
.render(RenderingStrategies.MYBATIS3);
1007+
1008+
String expectedStatement = "select u1.user_id, u1.user_name, u1.parent_id"
1009+
+ " from User u1 join User u2 on u1.user_id = u2.parent_id"
1010+
+ " where u2.user_id = #{parameters.p1,jdbcType=INTEGER}";
1011+
assertThat(selectStatement.getSelectStatement()).isEqualTo(expectedStatement);
1012+
1013+
List<User> rows = mapper.selectUsers(selectStatement);
1014+
1015+
assertThat(rows).hasSize(1);
1016+
User row = rows.get(0);
1017+
assertThat(row.getUserId()).isEqualTo(2);
1018+
assertThat(row.getUserName()).isEqualTo("Barney");
1019+
assertThat(row.getParentId()).isNull();
1020+
}
1021+
}
1022+
9611023
@Test
9621024
void testLimitAndOffsetAfterJoin() {
9631025
try (SqlSession session = sqlSessionFactory.openSession()) {

src/test/java/examples/joins/UserDynamicSQLSupport.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,22 +17,22 @@
1717

1818
import java.sql.JDBCType;
1919

20+
import org.mybatis.dynamic.sql.AliasableSqlTable;
2021
import org.mybatis.dynamic.sql.SqlColumn;
21-
import org.mybatis.dynamic.sql.SqlTable;
2222

2323
public class UserDynamicSQLSupport {
2424
public static final User user = new User();
2525
public final SqlColumn<Integer> userId = user.userId;
2626
public final SqlColumn<String> userName = user.userName;
2727
public final SqlColumn<Integer> parentId = user.parentId;
2828

29-
public static final class User extends SqlTable {
29+
public static final class User extends AliasableSqlTable<User> {
3030
public final SqlColumn<Integer> userId = column("user_id", JDBCType.INTEGER);
3131
public final SqlColumn<String> userName = column("user_name", JDBCType.VARCHAR);
3232
public final SqlColumn<Integer> parentId = column("parent_id", JDBCType.INTEGER);
3333

3434
public User() {
35-
super("User");
35+
super("User", User::new);
3636
}
3737
}
3838
}

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy