├── .gitignore ├── LICENSE ├── README.md ├── jactioncontroller ├── pom.xml └── src │ └── main │ ├── java │ └── me │ │ └── zzp │ │ └── jac │ │ ├── Controller.java │ │ ├── Dispatcher.java │ │ ├── Service.java │ │ └── ex │ │ ├── IllegalPathException.java │ │ └── RedirectionException.java │ └── resources │ └── META-INF │ └── web-fragment.xml ├── jactionview ├── pom.xml └── src │ └── main │ ├── java │ └── me │ │ └── zzp │ │ └── jav │ │ ├── ViewConfig.java │ │ └── ViewSetup.java │ └── resources │ └── META-INF │ └── web-fragment.xml ├── jactiverecord-el ├── README.md ├── pom.xml └── src │ └── main │ ├── java │ └── me │ │ └── zzp │ │ └── ar │ │ └── el │ │ ├── DatabaseSetup.java │ │ ├── RecordELResolver.java │ │ ├── ResolverSetup.java │ │ └── TableELResolver.java │ └── resources │ └── META-INF │ └── web-fragment.xml ├── jactiverecord ├── README.md ├── pom.xml └── src │ ├── main │ ├── java │ │ └── me │ │ │ └── zzp │ │ │ ├── ar │ │ │ ├── Association.java │ │ │ ├── DB.java │ │ │ ├── Lambda.java │ │ │ ├── Query.java │ │ │ ├── Record.java │ │ │ ├── Table.java │ │ │ ├── d │ │ │ │ ├── Dialect.java │ │ │ │ ├── HyperSQLDialect.java │ │ │ │ ├── MySQLDialect.java │ │ │ │ ├── PostgreSQLDialect.java │ │ │ │ ├── SQLiteDialect.java │ │ │ │ └── package-info.java │ │ │ ├── ex │ │ │ │ ├── DBOpenException.java │ │ │ │ ├── IllegalFieldNameException.java │ │ │ │ ├── IllegalTableNameException.java │ │ │ │ ├── SqlExecuteException.java │ │ │ │ ├── TransactionException.java │ │ │ │ ├── UndefinedAssociationException.java │ │ │ │ ├── UnsupportedDatabaseException.java │ │ │ │ └── package-info.java │ │ │ ├── package-info.java │ │ │ ├── pool │ │ │ │ ├── JdbcDataSource.java │ │ │ │ ├── SingletonConnection.java │ │ │ │ ├── SingletonDataSource.java │ │ │ │ ├── SingletonPreparedStatement.java │ │ │ │ ├── SingletonResultSet.java │ │ │ │ └── package-info.java │ │ │ └── sql │ │ │ │ ├── AbstractSqlBuilder.java │ │ │ │ ├── SqlBuilder.java │ │ │ │ ├── SqlSyntaxException.java │ │ │ │ ├── TSqlBuilder.java │ │ │ │ └── package-info.java │ │ │ └── util │ │ │ ├── Seq.java │ │ │ └── package-info.java │ └── resources │ │ └── META-INF │ │ └── services │ │ └── me.zzp.ar.d.Dialect │ └── test │ ├── java │ └── me │ │ └── zzp │ │ ├── ar │ │ └── sql │ │ │ └── TSqlBuilderTest.java │ │ ├── test │ │ ├── CompatibilityTest.java │ │ ├── CountActiveConnection.java │ │ ├── TestSuite.java │ │ └── TransactionTest.java │ │ └── util │ │ └── SeqTest.java │ └── resources │ └── META-INF │ └── services │ └── java.sql.Driver ├── java-on-rails ├── pom.xml └── src │ └── main │ └── resources │ └── META-INF │ └── web-fragment.xml └── pom.xml /.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | target/ 3 | !.mvn/wrapper/maven-wrapper.jar 4 | !**/src/main/**/target/ 5 | !**/src/test/**/target/ 6 | 7 | ### STS ### 8 | .apt_generated 9 | .classpath 10 | .factorypath 11 | .project 12 | .settings 13 | .springBeans 14 | .sts4-cache 15 | 16 | ### IntelliJ IDEA ### 17 | .idea 18 | *.iws 19 | *.iml 20 | *.ipr 21 | 22 | ### NetBeans ### 23 | /nbproject/private/ 24 | /nbbuild/ 25 | /dist/ 26 | /nbdist/ 27 | /.nb-gradle/ 28 | build/ 29 | !**/src/main/**/build/ 30 | !**/src/test/**/build/ 31 | 32 | ### VS Code ### 33 | .vscode/ 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | (The MIT License) 2 | 3 | Copyright (c) 2008-2014 Tom Preston-Werner 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the 'Software'), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Java on Rails 2 | 3 | Yet another Java MVC framework inspired by Ruby on Rails. 4 | 5 | * [jactiverecord](https://github.com/redraiment/java-on-rails/tree/main/jactiverecord): ORM module, implements ActiveRecord pattern in Java. 6 | * [jactiverecord-el](https://github.com/redraiment/java-on-rails/tree/main/jactiverecord-el): JSP expression language (EL) for jActiveRecord, provides Record & Table field accessor. 7 | * [jactioncontroller](https://github.com/redraiment/java-on-rails/tree/main/jactioncontroller): Controller module. 8 | * [jactionview](https://github.com/redraiment/java-on-rails/tree/main/jactionview): View module. 9 | -------------------------------------------------------------------------------- /jactioncontroller/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | 6 | me.zzp 7 | java-on-rails 8 | 1.0.0-SNAPSHOT 9 | 10 | 11 | jactioncontroller 12 | 1.0.0-SNAPSHOT 13 | jActionController 14 | jar 15 | 16 | 17 | 18 | jakarta.servlet 19 | jakarta.servlet-api 20 | provided 21 | 22 | 23 | -------------------------------------------------------------------------------- /jactioncontroller/src/main/java/me/zzp/jac/Controller.java: -------------------------------------------------------------------------------- 1 | package me.zzp.jac; 2 | 3 | import java.util.ArrayList; 4 | import java.util.HashMap; 5 | import java.util.List; 6 | import java.util.Map; 7 | import java.util.regex.Matcher; 8 | import java.util.regex.Pattern; 9 | import me.zzp.jac.ex.IllegalPathException; 10 | 11 | final class Controller { 12 | private final Pattern pattern; 13 | private final List names; 14 | 15 | Controller(String path) { 16 | names = new ArrayList<>(); 17 | 18 | StringBuilder regex = new StringBuilder(); 19 | int count = 0; 20 | int from = -1; 21 | boolean inside = false; 22 | for (int i = 0; i < path.length(); i++) { 23 | char c = path.charAt(i); 24 | switch (c) { 25 | case '{': { 26 | if (count == 0) { 27 | from = i + 1; 28 | inside = true; 29 | } 30 | count++; 31 | } break; 32 | case '}': { 33 | count--; 34 | if (count == 0) { 35 | String[] values = path.substring(from, i).split(":", 2); 36 | names.add(values[0]); 37 | if (values.length == 1) { 38 | regex.append("([^/]+?)"); 39 | } else { 40 | regex.append('(').append(values[1]).append(')'); 41 | } 42 | inside = false; 43 | } else if (count < 0) { 44 | throw new IllegalPathException(path); 45 | } 46 | } break; 47 | default: { 48 | if (!inside) { 49 | regex.append(c); 50 | if (c == '\\') { 51 | i++; 52 | regex.append(path.charAt(i)); 53 | } 54 | } 55 | } break; 56 | } 57 | } 58 | 59 | pattern = Pattern.compile(regex.toString()); 60 | } 61 | 62 | Map parse(String path) { 63 | Matcher matcher = pattern.matcher(path); 64 | if (matcher.matches() && matcher.groupCount() == names.size()) { 65 | Map params = new HashMap<>(); 66 | for (int i = 0; i < matcher.groupCount(); i++) { 67 | params.put(names.get(i), matcher.group(i + 1)); 68 | } 69 | return params; 70 | } else { 71 | return null; 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /jactioncontroller/src/main/java/me/zzp/jac/Dispatcher.java: -------------------------------------------------------------------------------- 1 | package me.zzp.jac; 2 | 3 | import java.io.IOException; 4 | import java.lang.reflect.Constructor; 5 | import java.lang.reflect.InvocationTargetException; 6 | import java.util.LinkedHashMap; 7 | import java.util.Map; 8 | import java.util.Scanner; 9 | import javax.servlet.ServletException; 10 | import javax.servlet.http.HttpServlet; 11 | import javax.servlet.http.HttpServletRequest; 12 | import javax.servlet.http.HttpServletResponse; 13 | 14 | public class Dispatcher extends HttpServlet { 15 | private static final Map> services = new LinkedHashMap<>(); 16 | 17 | public static synchronized void add(String pattern, Class type) { 18 | if (Service.class.isAssignableFrom(type)) { 19 | try { 20 | Constructor c = type.getConstructor(HttpServletRequest.class, HttpServletResponse.class); 21 | services.put(new Controller(pattern), c); 22 | } catch (NoSuchMethodException | SecurityException e) { 23 | System.err.println(e.getMessage()); 24 | } 25 | } 26 | } 27 | 28 | protected Service match(HttpServletRequest request, HttpServletResponse response) { 29 | String path = request.getPathInfo(); 30 | 31 | for (Map.Entry> service : services.entrySet()) { 32 | Map url = service.getKey().parse(path); 33 | if (url != null) { 34 | for (Map.Entry info : url.entrySet()) { 35 | request.setAttribute(info.getKey(), info.getValue()); 36 | } 37 | 38 | try { 39 | return service.getValue().newInstance(request, response); 40 | } catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { 41 | System.err.println(e.getMessage()); 42 | } 43 | } 44 | } 45 | 46 | return null; 47 | } 48 | 49 | @Override 50 | protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { 51 | match(request, response).query(); 52 | } 53 | 54 | @Override 55 | protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { 56 | match(request, response).create(); 57 | } 58 | 59 | @Override 60 | protected void doPut(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { 61 | Service service = match(request, response); 62 | if (service != null) { 63 | try (Scanner rin = new Scanner(request.getInputStream()).useDelimiter("\\A")) { 64 | String data = rin.hasNext()? rin.next(): ""; 65 | for (String pair : data.split("&")) { 66 | String[] item = pair.split("=", 2); 67 | if (item.length == 2) { 68 | service.params.put(item[0], item[1]); 69 | } 70 | } 71 | } 72 | service.update(); 73 | } 74 | } 75 | 76 | @Override 77 | protected void doDelete(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { 78 | match(request, response).delete(); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /jactioncontroller/src/main/java/me/zzp/jac/Service.java: -------------------------------------------------------------------------------- 1 | package me.zzp.jac; 2 | 3 | import java.io.IOException; 4 | import java.util.Arrays; 5 | import java.util.HashMap; 6 | import java.util.Map; 7 | import javax.servlet.ServletContext; 8 | import javax.servlet.ServletException; 9 | import javax.servlet.http.HttpServletRequest; 10 | import javax.servlet.http.HttpServletResponse; 11 | import javax.servlet.http.HttpSession; 12 | import me.zzp.jac.ex.RedirectionException; 13 | 14 | public abstract class Service { 15 | protected final ServletContext app; 16 | protected final HttpSession session; 17 | protected final HttpServletRequest request; 18 | protected final HttpServletResponse response; 19 | protected final Map params; 20 | 21 | public Service(HttpServletRequest request, HttpServletResponse response) { 22 | this.request = request; 23 | this.response = response; 24 | params = new HashMap<>(); 25 | session = request.getSession(); 26 | app = request.getServletContext(); 27 | 28 | for (Map.Entry entry : request.getParameterMap().entrySet()) { 29 | String name = entry.getKey(); 30 | String[] values = entry.getValue(); 31 | if (values.length == 1) { 32 | params.put(name, values[0]); 33 | } else { 34 | params.put(name, Arrays.asList(values)); 35 | } 36 | } 37 | } 38 | 39 | protected E get(String key) { 40 | Object value = null; 41 | if (params.containsKey(key)) { 42 | value = params.get(key); 43 | } else if (request.getAttribute(key) != null) { 44 | value = request.getAttribute(key); 45 | } else if (session.getAttribute(key) != null) { 46 | value = session.getAttribute(key); 47 | } else if (app.getAttribute(key) != null) { 48 | value = app.getAttribute(key); 49 | } 50 | return (E)value; 51 | } 52 | 53 | protected String getStr(String key) { 54 | Object value = get(key); 55 | return value != null? value.toString(): null; 56 | } 57 | 58 | protected int getInt(String key) { 59 | String value = getStr(key); 60 | try { 61 | return Integer.parseInt(value); 62 | } catch(NumberFormatException e) { 63 | return 0; 64 | } 65 | } 66 | 67 | protected void set(String key, Object value) { 68 | request.setAttribute(key, value); 69 | } 70 | 71 | protected void forward(String path) { 72 | try { 73 | request.getRequestDispatcher(path).forward(request, response); 74 | } catch (ServletException | IOException e) { 75 | throw new RedirectionException("forward", path, e); 76 | } 77 | } 78 | 79 | protected void redirect(String path) { 80 | if (path.startsWith("/")) { 81 | path = request.getContextPath().concat(path); 82 | } 83 | 84 | try { 85 | response.sendRedirect(path); 86 | } catch (IOException e) { 87 | throw new RedirectionException("redirect", path, e); 88 | } 89 | } 90 | 91 | public void create() { 92 | } 93 | 94 | public void update() { 95 | } 96 | 97 | public void delete() { 98 | } 99 | 100 | public void query() { 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /jactioncontroller/src/main/java/me/zzp/jac/ex/IllegalPathException.java: -------------------------------------------------------------------------------- 1 | package me.zzp.jac.ex; 2 | 3 | public final class IllegalPathException extends RuntimeException { 4 | public IllegalPathException(String path) { 5 | super(path); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /jactioncontroller/src/main/java/me/zzp/jac/ex/RedirectionException.java: -------------------------------------------------------------------------------- 1 | package me.zzp.jac.ex; 2 | 3 | public final class RedirectionException extends RuntimeException { 4 | public RedirectionException(String type, String path, Throwable e) { 5 | super(String.format("%s to %s failed", type, path), e); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /jactioncontroller/src/main/resources/META-INF/web-fragment.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | RESTful Dispatcher 5 | Dispatcher 6 | me.zzp.jac.Dispatcher 7 | 1 8 | 9 | 10 | Dispatcher 11 | /* 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /jactionview/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | 6 | me.zzp 7 | java-on-rails 8 | 1.0.0-SNAPSHOT 9 | 10 | 11 | jactionview 12 | 1.0.0-SNAPSHOT 13 | jActionView 14 | jar 15 | 16 | 17 | 18 | jakarta.servlet 19 | jakarta.servlet-api 20 | provided 21 | 22 | 23 | -------------------------------------------------------------------------------- /jactionview/src/main/java/me/zzp/jav/ViewConfig.java: -------------------------------------------------------------------------------- 1 | package me.zzp.jav; 2 | 3 | import java.util.Collections; 4 | import java.util.Enumeration; 5 | import java.util.HashMap; 6 | import java.util.Map; 7 | import javax.servlet.ServletConfig; 8 | import javax.servlet.ServletContext; 9 | 10 | final class ViewConfig implements ServletConfig { 11 | private final ServletContext context; 12 | private final Map params; 13 | private final String name; 14 | private final String url; 15 | 16 | ViewConfig(ServletContext context, String jsp, String prefix) { 17 | this.context = context; 18 | this.name = jsp.substring(prefix.length()) 19 | .replaceAll("/", "-") 20 | .replaceAll("\\.jsp", "-view") 21 | .toUpperCase(); 22 | this.url = "/".concat(name.toLowerCase()); 23 | 24 | params = new HashMap<>(); 25 | params.put("jsp-file", jsp); 26 | params.put("jspFile", jsp); 27 | } 28 | 29 | public String getUrl() { 30 | return url; 31 | } 32 | 33 | @Override 34 | public String getServletName() { 35 | return name; 36 | } 37 | 38 | @Override 39 | public ServletContext getServletContext() { 40 | return context; 41 | } 42 | 43 | @Override 44 | public String getInitParameter(String key) { 45 | return params.get(key); 46 | } 47 | 48 | @Override 49 | public Enumeration getInitParameterNames() { 50 | return Collections.enumeration(params.keySet()); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /jactionview/src/main/java/me/zzp/jav/ViewSetup.java: -------------------------------------------------------------------------------- 1 | package me.zzp.jav; 2 | 3 | import java.util.LinkedList; 4 | import java.util.List; 5 | import java.util.Set; 6 | import javax.servlet.ServletContext; 7 | import javax.servlet.ServletContextEvent; 8 | import javax.servlet.ServletContextListener; 9 | import javax.servlet.ServletException; 10 | import javax.servlet.ServletRegistration.Dynamic; 11 | import org.apache.jasper.servlet.JspServlet; 12 | 13 | public class ViewSetup implements ServletContextListener { 14 | 15 | private List listJsp(ServletContext context, String root) { 16 | List files = new LinkedList<>(); 17 | Set paths = context.getResourcePaths(root); 18 | if (paths != null) { 19 | for (String path : paths) { 20 | if (path.endsWith(".jsp")) { 21 | files.add(path); 22 | } else if (path.endsWith("/")) { 23 | files.addAll(listJsp(context, path)); 24 | } 25 | } 26 | } 27 | return files; 28 | } 29 | 30 | @Override 31 | public void contextInitialized(ServletContextEvent e) { 32 | ServletContext context = e.getServletContext(); 33 | String paths = context.getInitParameter("jactionview-paths"); 34 | if (paths == null || paths.isEmpty()) { 35 | return; 36 | } 37 | 38 | for (String root : paths.split(",")) { 39 | if (!root.endsWith("/")) { 40 | root = root.concat("/"); 41 | } 42 | 43 | for (String file : listJsp(context, root)) { 44 | ViewConfig view = new ViewConfig(context, file, root); 45 | try { 46 | JspServlet jsp = context.createServlet(JspServlet.class); 47 | jsp.init(view); 48 | Dynamic mapping = context.addServlet(view.getServletName(), jsp); 49 | mapping.addMapping(view.getUrl()); 50 | } catch (ServletException ex) { 51 | System.err.println(ex.getMessage()); 52 | } 53 | } 54 | } 55 | } 56 | 57 | @Override 58 | public void contextDestroyed(ServletContextEvent e) { 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /jactionview/src/main/resources/META-INF/web-fragment.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | me.zzp.jav.ViewSetup 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /jactiverecord-el/README.md: -------------------------------------------------------------------------------- 1 | # jActiveRecord-EL 2 | 3 | `jActiveRecord-EL`是[jActiveRecord](https://github.com/redraiment/jactiverecord)的辅助项目,简化在EL表达式中访问数据的方法,做到像操作普通`JavaBean`一样操作`Record`和`Table`类型的对象。适合采用了`jActiveRecord`的`Web`项目。 4 | 5 | * 项目主页:[http://github.com/redraiment/jactiverecord-el](http://github.com/redraiment/jactiverecord-el) 6 | * javadoc:[http://zzp.me/jactiverecord-el/](http://zzp.me/jactiverecord-el/) 7 | * jActiveRecord:[http://github.com/redraiment/jactiverecord](http://github.com/redraiment/jactiverecord) 8 | 9 | `jActiveRecord-EL`同样使用`Maven`管理,在`pom.xml`中添加如下依赖即可: 10 | 11 | ```xml 12 | 13 | me.zzp 14 | jactiverecord-el 15 | 1.2 16 | 17 | ``` 18 | 19 | # 访问Record属性 20 | 21 | 假设`Record`实例`user`有一个字符串类型的属性`name`,如果不使用`jActiveRecord-EL`,要在EL表达式中获得该属性的值,方法是: 22 | 23 | ```xml 24 |

${user.get("name")}

25 | ``` 26 | 27 | 采用`jActiveRecord-EL`之后,方法是: 28 | 29 | ```xml 30 |

${user.name}

31 | ``` 32 | 33 | `jActiveRecord-EL`简化了在EL表达式中访问`Record`属性的方法,能像访问`JavaBean`属性一样地访问`Record`的数据。 34 | 35 | # 访问Table方法 36 | 37 | `jActiveRecord-EL`同样简化了访问`Table`对象的方法,支持`all`、`first`、`last`和索引四种查询方式: 38 | 39 | * `all`:调用`Table#all()`。即`${User.all}`等价于`${User.all()}` 40 | * `first`:调用`Table#first()`。即`${User.first}`等价于`${User.first()}` 41 | * `last`:调用`Table#last()`。即`${User.last}`等价于`${User.last()}` 42 | * `索引`:调用`Table#find(int id)`。即`${User[1]}`等价于`${User.find(1)}` 43 | 44 | *注意* `${User[1]}`与`${User.all[1]}`的意义并不相同,前者返回表中`id`等于1的记录;后者返回所有记录(all)中第*二*条记录(索引从0开始)。 45 | 46 | # 配置 47 | 48 | ## 增强EL表达式 49 | 50 | 要使用jActiveRecord-EL,需要在`web.xml`中添加如下信息: 51 | 52 | ```xml 53 | 54 | me.zzp.ar.el.ResolverSetup 55 | 56 | ``` 57 | 58 | ## 骆驼命名法(可选) 59 | 60 | `JavaBean`属性的命名规则为骆驼命名法,例如“createdAt”;而数据库表的字段通常采用下划线命名法,例如“created_at”。该选项默认开启,即`${user.created_at}`与`${user.createdAt}`等价。在`web.xml`中添加如下上下文参数即可关闭自动转换开关: 61 | 62 | ```xml 63 | 64 | jactiverecord-el-camel-case 65 | false 66 | 67 | ``` 68 | 69 | ## 创建数据库对象(可选) 70 | 71 | 在`web`项目中使用`jActiveRecord`,通常第一步就是通过数据源(`javax.sql.DataSource`)创建数据库对象(`me.zzp.ar.DB`)。因此`jActiveRecord-EL`提供了另一个上下文监听器,在启动服务器的时候自动创建数据库对象,并添加到上下文对象的属性中,设置方法如下: 72 | 73 | ```xml 74 | 75 | me.zzp.ar.el.DatabaseSetup 76 | 77 | 78 | jactiverecord-el-data-source 79 | java:/comp/env/jdbc/DataSource 80 | 81 | ``` 82 | 83 | ## 重命名属性名(可选) 84 | 85 | `DatabaseSetup`创建的上下文属性名默认为“dbo”,即在`Servlet`中通过`getServletContext().getAttribute("dbo")`获得数据库对象。如果你不喜欢“dbo”这个名字,可指定以下信息自定义属性名: 86 | 87 | ```xml 88 | 89 | jactiverecord-el-attribute-name 90 | database 91 | 92 | ``` 93 | 94 | 这样,属性名就改成了database。 95 | -------------------------------------------------------------------------------- /jactiverecord-el/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | 6 | me.zzp 7 | java-on-rails 8 | 1.0.0-SNAPSHOT 9 | 10 | 11 | jactiverecord-el 12 | 1.2 13 | jar 14 | 15 | jActiveRecord-EL 16 | Enhance Expression Language in JSP for jActiveRecord 17 | 18 | 19 | 20 | me.zzp 21 | jactiverecord 22 | 23 | 24 | jakarta.servlet 25 | jakarta.servlet-api 26 | provided 27 | 28 | 29 | 30 | 31 | 32 | 33 | maven-source-plugin 34 | 35 | 36 | maven-javadoc-plugin 37 | 38 | 39 | maven-gpg-plugin 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /jactiverecord-el/src/main/java/me/zzp/ar/el/DatabaseSetup.java: -------------------------------------------------------------------------------- 1 | package me.zzp.ar.el; 2 | 3 | import javax.naming.InitialContext; 4 | import javax.naming.NamingException; 5 | import javax.servlet.ServletContext; 6 | import javax.servlet.ServletContextEvent; 7 | import javax.servlet.ServletContextListener; 8 | import javax.sql.DataSource; 9 | import me.zzp.ar.DB; 10 | 11 | /** 12 | *

在上下文属性中添加数据库对象。

13 | *

1. 欲自动生成数据库对象,需要在web.xml添加此监听器:

14 | *
15 | *
<listener>
16 |   <listener-class>me.zzp.ar.el.DatabaseSetup</listener-class>
17 | </listener>
18 | *
19 | *

同时添加上下文参数,指定数据源的路径:

20 | *
21 | *
<context-param>
22 |   <param-name>jactiverecord-el-data-source</param-name>
23 |   <param-value>java:/comp/env/jdbc/DataSource</param-value>
24 | </context-param>
25 | *
26 | *

此时,在Servlet中调用 getServletContext().getAttribute("dbo") 27 | * 就能获得{@link me.zzp.ar.DB}对象。

28 | *

2. 其中“dbo”为默认的属性名,要修改这个名字可通过在web.xml中添加上下文参数:

29 | *
30 | *
<context-param>
31 |   <param-name>jactiverecord-el-attribute-name</param-name>
32 |   <param-value>database</param-value>
33 | </context-param>
34 | *
35 | *

这样,属性名就改成了database

36 | * @author redraiment 37 | * @since 1.1 38 | * @see me.zzp.ar.DB 39 | */ 40 | public class DatabaseSetup implements ServletContextListener { 41 | /** 42 | *

初始化数据库对象。

43 | * @param e 上下文事件对象 44 | */ 45 | @Override 46 | public void contextInitialized(ServletContextEvent e) { 47 | ServletContext context = e.getServletContext(); 48 | String path = context.getInitParameter("jactiverecord-el-data-source"); 49 | if (path == null || path.isEmpty()) { 50 | return; 51 | } 52 | String name = context.getInitParameter("jactiverecord-el-attribute-name"); 53 | if (name == null || name.isEmpty()) { 54 | name = "dbo"; 55 | } 56 | 57 | try { 58 | DataSource pool = InitialContext.doLookup(path); 59 | DB dbo = DB.open(pool); 60 | context.setAttribute(name, dbo); 61 | } catch (NamingException ex) { 62 | System.err.println(ex.getMessage()); 63 | } 64 | } 65 | 66 | /** 67 | * 什么都没做。 68 | * @param e 上下文事件对象 69 | */ 70 | @Override 71 | public void contextDestroyed(ServletContextEvent e) { 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /jactiverecord-el/src/main/java/me/zzp/ar/el/RecordELResolver.java: -------------------------------------------------------------------------------- 1 | package me.zzp.ar.el; 2 | 3 | import java.beans.FeatureDescriptor; 4 | import java.util.ArrayList; 5 | import java.util.Iterator; 6 | import java.util.List; 7 | import javax.el.ELContext; 8 | import javax.el.ELResolver; 9 | import me.zzp.ar.Record; 10 | 11 | /** 12 | *

me.zzp.ar.Record的EL表达式解析器。 13 | * 简化Record对象方法调用的方式。

14 | * @author redraiment 15 | * @since 1.0 16 | */ 17 | public final class RecordELResolver extends ELResolver { 18 | private final boolean camelCase; 19 | 20 | public RecordELResolver() { 21 | this(false); 22 | } 23 | 24 | public RecordELResolver(boolean camelCase) { 25 | this.camelCase = camelCase; 26 | } 27 | 28 | private String getKey(Object property) { 29 | String key = property.toString(); 30 | return camelCase? key.replaceAll("(?=[A-Z])", "_").toLowerCase(): key; 31 | } 32 | 33 | /** 34 | *

像访问普通JavaBean一样访问Record中的字段。

35 | *

${user.name}等价于${user.get("name")}

36 | * @param context EL表达式上下文 37 | * @param base Table对象 38 | * @param property 属性 39 | * @return 返回相应属性的值 40 | */ 41 | @Override 42 | public Object getValue(ELContext context, Object base, Object property) { 43 | if (base != null && base instanceof Record && property != null) { 44 | context.setPropertyResolved(true); 45 | Record record = (Record) base; 46 | return record.get(getKey(property)); 47 | } else { 48 | context.setPropertyResolved(false); 49 | return null; 50 | } 51 | } 52 | 53 | /** 54 | * 获取属性在数据库中相应的类型。 55 | * @param context EL表达式上下文 56 | * @param base Table对象 57 | * @param property 属性 58 | * @return 返回相应属性的类型 59 | */ 60 | @Override 61 | public Class getType(ELContext context, Object base, Object property) { 62 | if (base != null && base instanceof Record && property != null) { 63 | context.setPropertyResolved(true); 64 | Record record = (Record) base; 65 | Object o = record.get(getKey(property)); 66 | return o == null? null: o.getClass(); 67 | } else { 68 | context.setPropertyResolved(false); 69 | return null; 70 | } 71 | } 72 | 73 | /** 74 | *

设置属性值。

75 | *

等价于调用Record#set(String name, Object value)

76 | * @param context EL表达式上下文 77 | * @param base Table对象 78 | * @param property 属性 79 | * @param value 值 80 | */ 81 | @Override 82 | public void setValue(ELContext context, Object base, Object property, Object value) { 83 | if (base != null && base instanceof Record && property != null) { 84 | context.setPropertyResolved(true); 85 | Record record = (Record) base; 86 | record.set(getKey(property), value); 87 | } else { 88 | context.setPropertyResolved(false); 89 | } 90 | } 91 | 92 | /** 93 | * 均可写。 94 | * @param context EL表达式上下文 95 | * @param base Table对象 96 | * @param property 属性 97 | * @return false 98 | */ 99 | @Override 100 | public boolean isReadOnly(ELContext context, Object base, Object property) { 101 | if (base != null && base instanceof Record && property != null) { 102 | context.setPropertyResolved(true); 103 | return false; 104 | } else { 105 | context.setPropertyResolved(false); 106 | return false; 107 | } 108 | } 109 | 110 | /** 111 | * 返回由列名组成的列表。 112 | * @param context EL表达式上下文 113 | * @param base Table对象 114 | * @return 包含当前表的所有列名 115 | */ 116 | @Override 117 | public Iterator getFeatureDescriptors(ELContext context, Object base) { 118 | List list = new ArrayList<>(); 119 | if (base != null && base instanceof Record) { 120 | Record record = (Record) base; 121 | for (String column : record.columnNames()) { 122 | FeatureDescriptor feature = new FeatureDescriptor(); 123 | feature.setDisplayName(column); 124 | feature.setName(column); 125 | feature.setShortDescription(column); 126 | feature.setHidden(false); 127 | feature.setExpert(false); 128 | feature.setPreferred(true); 129 | list.add(feature); 130 | } 131 | } 132 | return list.iterator(); 133 | } 134 | 135 | /** 136 | * 属性为字符串类型。 137 | * @param context EL表达式上下文 138 | * @param base Table对象 139 | * @return String.class 140 | */ 141 | @Override 142 | public Class getCommonPropertyType(ELContext context, Object base) { 143 | return String.class; 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /jactiverecord-el/src/main/java/me/zzp/ar/el/ResolverSetup.java: -------------------------------------------------------------------------------- 1 | package me.zzp.ar.el; 2 | 3 | import javax.servlet.ServletContext; 4 | import javax.servlet.ServletContextEvent; 5 | import javax.servlet.ServletContextListener; 6 | import javax.servlet.jsp.JspApplicationContext; 7 | import javax.servlet.jsp.JspFactory; 8 | 9 | /** 10 | *

加载自定义的EL表达式解析器。

11 | *

1. 欲使用jActiveRecord-EL,需要在web.xml添加此监听器:

12 | *
13 | *
<listener>
14 |   <listener-class>me.zzp.ar.el.ResolverSetup</listener-class>
15 | </listener>
16 | *
17 | *

之后在EL表达式中就能像操作普通JavaBean一样操作 {@link me.zzp.ar.Table} 18 | * 和 {@link me.zzp.ar.Record}。

19 | *

2. jActiveRecord-EL会自动将骆驼法命名的属性转换为下划线命名, 20 | * 欲停止这个特性,可在web.xml中添加上下文参数:

21 | *
22 | *
<context-param>
23 |   <param-name>jactiverecord-el-camel-case</param-name>
24 |   <param-value>false</param-value>
25 | </context-param>
26 | *
27 | *

开启了该选项之后,${user.createdAt}等价于${user.created_at}

28 | * @author redraiment 29 | * @since 1.0 30 | * @see TableELResolver 31 | * @see RecordELResolver 32 | */ 33 | public class ResolverSetup implements ServletContextListener { 34 | /** 35 | *

加载TableELResolverRecordELResolver

36 | * @param e 上下文事件对象 37 | */ 38 | @Override 39 | public void contextInitialized(ServletContextEvent e) { 40 | ServletContext context = e.getServletContext(); 41 | String param = context.getInitParameter("jactiverecord-el-camel-case"); 42 | boolean camelCase = !"false".equalsIgnoreCase(param); 43 | 44 | JspFactory factory = JspFactory.getDefaultFactory(); 45 | JspApplicationContext resolvers = factory.getJspApplicationContext(context); 46 | resolvers.addELResolver(new TableELResolver()); 47 | resolvers.addELResolver(new RecordELResolver(camelCase)); 48 | } 49 | 50 | /** 51 | * 什么都没做。 52 | * @param e 上下文事件对象 53 | */ 54 | @Override 55 | public void contextDestroyed(ServletContextEvent e) { 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /jactiverecord-el/src/main/java/me/zzp/ar/el/TableELResolver.java: -------------------------------------------------------------------------------- 1 | package me.zzp.ar.el; 2 | 3 | import java.beans.FeatureDescriptor; 4 | import java.util.ArrayList; 5 | import java.util.Arrays; 6 | import java.util.Iterator; 7 | import java.util.List; 8 | import javax.el.ELContext; 9 | import javax.el.ELResolver; 10 | import me.zzp.ar.Record; 11 | import me.zzp.ar.Table; 12 | 13 | /** 14 | *

me.zzp.ar.Table的EL表达式解析器。 15 | * 简化Table对象方法调用的方式。

16 | * @author redraiment 17 | * @since 1.0 18 | */ 19 | public final class TableELResolver extends ELResolver { 20 | /** 21 | *

支持allfirstlast和索引四种查询方式:

22 | *
    23 | *
  • all:调用Table#all()。即${User.all}等价于${User.all()}
  • 24 | *
  • first:调用Table#first()。即${User.first}等价于${User.first()}
  • 25 | *
  • last:调用Table#last()。即${User.last}等价于${User.last()}
  • 26 | *
  • 索引:调用Table#find(int id)。即${User[1]}等价于${User.find(1)}
  • 27 | *
28 | * @param context EL表达式上下文 29 | * @param base Table对象 30 | * @param property 属性 31 | * @return 返回相应的方法调用结果 32 | */ 33 | @Override 34 | public Object getValue(ELContext context, Object base, Object property) { 35 | if (base != null && base instanceof Table && property != null) { 36 | context.setPropertyResolved(true); 37 | Table table = (Table) base; 38 | if (property instanceof String) { 39 | if (property.equals("all")) { 40 | return table.all(); 41 | } else if (property.equals("first")) { 42 | return table.first(); 43 | } else if (property.equals("last")) { 44 | return table.last(); 45 | } 46 | } else if (property instanceof Number) { 47 | Number index = (Number) property; 48 | return table.find(index.intValue()); 49 | } 50 | return null; 51 | } else { 52 | context.setPropertyResolved(false); 53 | return null; 54 | } 55 | } 56 | 57 | /** 58 | * 如果属性值为all,则返回 {@link java.util.List} 类型; 59 | * 否则返回 {@link me.zzp.ar.Record} 类型。 60 | * @param context EL表达式上下文 61 | * @param base Table对象 62 | * @param property 属性 63 | * @return 返回相应属性的类型 64 | */ 65 | @Override 66 | public Class getType(ELContext context, Object base, Object property) { 67 | if (base != null && base instanceof Table && property != null) { 68 | context.setPropertyResolved(true); 69 | if (property instanceof String) { 70 | if (property.equals("all")) { 71 | return List.class; 72 | } else if (property.equals("first") || property.equals("last")) { 73 | return Record.class; 74 | } 75 | } else if (property instanceof Number) { 76 | return Record.class; 77 | } 78 | return null; 79 | } else { 80 | context.setPropertyResolved(false); 81 | return null; 82 | } 83 | } 84 | 85 | /** 86 | * Table不允许修改。 87 | * @param context EL表达式上下文 88 | * @param base Table对象 89 | * @param property 属性 90 | * @param value 值 91 | */ 92 | @Override 93 | public void setValue(ELContext context, Object base, Object property, Object value) { 94 | context.setPropertyResolved(base != null && base instanceof Table); 95 | } 96 | 97 | /** 98 | * 总是返回true。 99 | * @param context EL表达式上下文 100 | * @param base Table对象 101 | * @param property 属性 102 | * @return true 103 | */ 104 | @Override 105 | public boolean isReadOnly(ELContext context, Object base, Object property) { 106 | if (base != null && base instanceof Table) { 107 | context.setPropertyResolved(true); 108 | return true; 109 | } else { 110 | context.setPropertyResolved(false); 111 | return false; 112 | } 113 | } 114 | 115 | /** 116 | * 仅返回all、first和last三个方法名。 117 | * @param context EL表达式上下文 118 | * @param base Table对象 119 | * @return 包含all、first和last三个方法名 120 | */ 121 | @Override 122 | public Iterator getFeatureDescriptors(ELContext context, Object base) { 123 | List list = new ArrayList<>(); 124 | if (base != null && base instanceof Table) { 125 | for (String column : Arrays.asList("all", "first", "last")) { 126 | FeatureDescriptor feature = new FeatureDescriptor(); 127 | feature.setDisplayName(column); 128 | feature.setName(column); 129 | feature.setShortDescription(column); 130 | feature.setHidden(false); 131 | feature.setExpert(false); 132 | feature.setPreferred(true); 133 | list.add(feature); 134 | } 135 | } 136 | return list.iterator(); 137 | } 138 | 139 | /** 140 | * 属性为字符串类型。 141 | * @param context EL表达式上下文 142 | * @param base Table对象 143 | * @return String.class 144 | */ 145 | @Override 146 | public Class getCommonPropertyType(ELContext context, Object base) { 147 | return String.class; 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /jactiverecord-el/src/main/resources/META-INF/web-fragment.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | me.zzp.ar.el.ResolverSetup 5 | 6 | 7 | -------------------------------------------------------------------------------- /jactiverecord/README.md: -------------------------------------------------------------------------------- 1 | # jActiveRecord 2 | 3 | `jActiveRecord`是我根据自己的喜好用`Java`实现的对象关系映射(ORM)库,灵感来自`Ruby on Rails`的`ActiveRecord`。它拥有以下特色: 4 | 5 | 1. 零配置:无XML配置文件、无Annotation注解。 6 | 1. 零依赖:不依赖任何第三方库,运行环境为Java 6或以上版本。 7 | 1. 零SQL:无需显式地写任何SQL语句,甚至多表关联、分页等高级查询亦是如此。 8 | 1. 动态性:和其他库不同,无需为每张表定义一个相对应的静态类。表、表对象、行对象等都能动态创建和动态获取。 9 | 1. 简化:`jActiveRecord`虽是模仿`ActiveRecord`,它同时做了一些简化。例如,所有的操作仅涉及DB、Table和Record三个类,并且HasMany、HasAndBelongsToMany等关联对象职责单一化,容易理解。 10 | 1. 支持多数据库访问 11 | 1. 多线程安全 12 | 1. 支持事务 13 | 14 | # 入门 15 | 16 | 参考[Rails For Zombies](http://railsforzombies.org/),我们一步一步创建一套僵尸微博系统的数据层。 17 | 18 | ## 安装 19 | 20 | `jActiveRecord`采用`Maven`维护,并已发布到中央库,仅需在`pom.xml`中添加如下声明: 21 | 22 | ```xml 23 | 24 | me.zzp 25 | jactiverecord 26 | 2.3 27 | 28 | ``` 29 | 30 | ## 连接数据库 31 | 32 | `jActiveRecord`的入口是`me.zzp.ar.DB`类,通过open这个静态方法创建数据库对象,open方法的参数与`java.sql.DriverManager#getConnection`兼容。 33 | 34 | ```java 35 | DB sqlite3 = DB.open("jdbc:sqlite::memory:"); 36 | ``` 37 | 38 | `DB#open`默认只创建一个数据库连接,因此内存型数据库能正常使用;真实项目中推荐使用`C3P0`等创建连接池。作为演示,此处用sqlite创建一个内存数据库。 39 | 40 | ## 创建表 41 | 42 | 首先要创建一张用户信息表,此处的用户当然是僵尸(Zombie),包含名字(name)和墓地(graveyard)两个信息。 43 | 44 | ```java 45 | Table Zombie = sqlite3.createTable("zombies", "name text", "graveyard text"); 46 | ``` 47 | 48 | `createTable`方法的第一个参数是数据库表的名字,之后可以跟随任意个描述字段的参数,格式是名字+类型,用空格隔开。 49 | 50 | `createTable`方法会自动添加一个自增长(auto increment)的`id`字段作为主键。由于各个数据库实现自增长字段的方式不同,目前`jActiveRecord`的“创建表”功能支持如下数据库: 51 | 52 | * HyperSQL 53 | * MySQL 54 | * PostgreSQL 55 | * SQLite 56 | 57 | 如果你使用的数据库不在上述列表中,可以自己实现`me.zzp.ar.d.Dialect`接口,并添加到`META-INF/services/me.zzp.ar.d.Dialect`。`jActiveRecord`采用`Java 6`的`ServiceLoader`自动加载实现`Dialect`接口的类。 58 | 59 | 此外`jActiveRecord`还会额外添加`created_at`和`updated_at`两个字段,类型均为`timestamp`,分别保存记录被创建和更新的时间。因此,上述代码总共创建了5个字段:`id`、`name`、`graveyard`、`created_at`和`updated_at`。 60 | 61 | ## 添加 62 | 63 | ```java 64 | Table Zombie = sqlite3.active("zombies"); 65 | Zombie.create("name:", "Ash", "graveyard:", "Glen Haven Memorial Cemetery"); 66 | Zombie.create("name", "Bob", "graveyard", "Chapel Hill Cemetery"); 67 | Zombie.create("graveyard", "My Fathers Basement", "name", "Jim"); 68 | ``` 69 | 70 | 首先用`DB#active`获取之前创建的表对象,然后使用`Table#create`新增一条记录(并且立即返回刚创建的记录)。该方法可使用“命名参数”,来突显每个值的含义。由于Java语法不支持命名参数,因此列名末尾允许带一个冗余的冒号,即“name:”与“name”是等价的;此外键值对顺序无关,因此第三条名为“Jim”的僵尸记录也能成功创建。 71 | 72 | ## 查询 73 | 74 | `jActiveRecord`提供了下列查询方法: 75 | 76 | * `Record find(int id)`:返回指定`id`的记录。 77 | * `List all()`:返回符合约束的所有记录。 78 | * `List paging(int page, int size)`:基于`all()`的分页查询,`page`从`0`开始。 79 | * `Record first()`:基于`all()`,返回按`id`排序的第一条记录。 80 | * `Record last()`:基于`all()`,返回按`id`排序的最后一条记录。 81 | * `List where(String condition, Object... args)`:基于`all()`,返回符合条件的所有记录。条件表达式兼容`java.sql.PreparedStatement`。 82 | * `Record first(String condition, Object... args)`:基于`where()`,返回按`id`排序的第一条记录。 83 | * `Record last(String condition, Object... args)`:基于`where()`,返回按`id`排序的最后一条记录。 84 | * `List findBy(String key, Object value)`:基于`all()`,返回指定列与`value`相等的所有记录。 85 | * `Record findA(String key, Object value)`:基于`findBy()`,返回按`id`排序的第一条记录。 86 | 87 | `first`、`last`和`find`等方法仅返回一条记录;另一些方法可能返回多条记录,因此返回`List`。 88 | 89 | 例如,获得`id`为3的僵尸有以下方法: 90 | 91 | ```java 92 | Zombie.find(3); 93 | Zombie.findA("name", "Jim"); 94 | Zombie.first("graveyard like ?", "My Father%"); 95 | ``` 96 | 97 | 数据库返回的记录被包装成`Record`对象,使用`Record#get`获取数据。借助泛型,能根据左值自动转换数据类型: 98 | 99 | ```java 100 | Record jim = Zombie.find(3); 101 | int id = jim.get("id"); 102 | String name = jim.get("name"); 103 | Timestamp createdAt = jim.get("created_at"); 104 | ``` 105 | 106 | 此外,`Record`同样提供了诸如`getInt`、`getStr`等常用类型的强制转换接口。 107 | 108 | `jActiveRecord`不使用`Bean`,因为`Bean`不通用,你不得不为每张表创建一个相应的`Bean`类;使用`Bean`除了能在编译期检查`getter`和`setter`的名字是否有拼写错误,没有任何好处; 109 | 110 | ## 更新 111 | 112 | 通过查询获得目标对象,接着可以做一些更新操作。例如将编号为3的僵尸的目的改成“Benny Hills Memorial”。 113 | 114 | 调用`Record#set`方法可更新记录中的值,然后调用`Record#save`或`Table#update`保存修改结果;或者调用`Record#update`一步完成更新和保存操作,该方法和`create`一样接受任意多个命名参数。 115 | 116 | ```java 117 | Record jim = Zombie.find(3); 118 | jim.set("graveyard", "Benny Hills Memorial").save(); 119 | jim.update("graveyard:", "Benny Hills Memorial"); // Same with above 120 | ``` 121 | 122 | ## 删除 123 | 124 | `Table#delete`和`Record#destroy`都能删除一条记录,`Table#purge`能删除当前约束下所有的记录。 125 | 126 | ```java 127 | Zombie.find(1).destroy(); 128 | Zombie.delete(Zombie.find(1)); // Same with above 129 | ``` 130 | 131 | 上述代码功能相同:删除`id`为1的僵尸。 132 | 133 | ## 关联 134 | 135 | 到了最精彩的部分了!ORM库除了将记录映射成对象,还要将表之间的关联信息面向对象化。 136 | 137 | `jActiveRecord`提供与RoR一样的四种关联关系,并做了简化: 138 | 139 | * Table#belongsTo 140 | * Table#hasOne 141 | * Table#hasMany 142 | * Table#hasAndBelongsToMany 143 | 144 | 每个方法接收一个字符串参数`name`作为关系的名字,并返回`Association`关联对象,拥有以下三个方法: 145 | 146 | * by:指定外键的名字,默认使用`name` + "_id"作为外键的名字。 147 | * in:指定关联表的名字,默认与`name`相同。 148 | * through:关联组合,参数为其他已经指定的关联的名字。即通过其他关联实现跨表访问(`join`多张表)。 149 | 150 | ### 一对多 151 | 152 | 回到僵尸微博系统的问题上,上面的章节仅创建了一张用户表,现在创建另一张表`tweets`保存微博信息: 153 | 154 | ```java 155 | Table Tweet = sqlite3.createTable("tweets", "zombie_id int", "content text"); 156 | ``` 157 | 158 | 其中`zombie_id`作为外键与`zombies`表的`id`像关联。即每个僵尸有多条相关联的微博,而每条微博仅有一个相关联的僵尸。`jActiveRecord`中用`hasMany`和`belongsTo`来描述这种“一对多”的关系。其中`hasMany`在“一”方使用,`belongsTo`在“多”放使用(即外键所在的表)。 159 | 160 | ```java 161 | Zombie.hasMany("tweets").by("zombie_id"); 162 | Tweet.belongsTo("zombie").by("zombie_id").in("zombies"); 163 | ``` 164 | 165 | 接着,就能通过关联名从`Record`中获取关联对象了。例如,获取`Jim`的所有微博: 166 | 167 | ```java 168 | Record jim = Zombie.find(3); 169 | Table jimTweets = jim.get("tweets"); 170 | for (Record tweet : jimTweets.all()) { 171 | // ... 172 | } 173 | ``` 174 | 175 | 或者根据微博获得相应的僵尸信息: 176 | 177 | ```java 178 | Record zombie = Tweet.find(1).get("zombie"); 179 | ``` 180 | 181 | 你可能已经注意到了:`hasMany`会返回多条记录,因此返回`Table`类型;`belongsTo`永远只返回一条记录,因此返回`Record`。此外,还有一种特殊的一对多关系:`hasOne`,即“多”方有且仅有一条记录。`hasOne`的用法和`hasMany`相同,只是返回值是`Record`而不是`Table`。 182 | 183 | ### 关联组合 184 | 185 | 让我们再往微博系统中加入“评论”功能: 186 | 187 | ```java 188 | Table Comment = sqlite3.createTable("comments", "zombie_id int", "tweet_id", "content text"); 189 | ``` 190 | 191 | 一条微博可以收到多条评论;而一个僵尸有多条微博。因此,僵尸和收到的评论是一种组合的关系:僵尸`hasMany`微博`hasMany`评论。`jActiveRecord`提供`through`描述这种组合的关联关系。 192 | 193 | ```java 194 | Zombie.hasMany("tweets").by("zombie_id"); // has defined above 195 | Zombie.hasMany("receive_comments").by("tweet_id").through("tweets"); 196 | Zombie.hasMany("send_comments").by("zombie_id").in("comments"); 197 | ``` 198 | 199 | 上面的规则描述了`Zombie`首先能找到`Tweet`,借助`Tweet.tweet_id`又能找到`Comment`。第三行代码描述`Zombie`通过`Comment`的`zombie_id`可直接获取发出去的评论。 200 | 201 | 事实上,`through`可用于组合任意类型的关联,例如`hasAndBelongsToMany`依赖`hasOne`、`belongsTo`依赖另一条`belongsTo`…… 202 | 203 | ### 多对多 204 | 205 | RoR中多对多关联有`has_many through`和`has_and_belongs_to_many`两种方法,且功能上有重叠之处。`jActiveRecord`仅保留`hasAndBelongsToMany`这一种方式来描述多对多关联。多对多关联要求有一张独立的映射表,记录映射关系。即两个“多”方都没有包含彼此的外键,而是借助第三张表同时保存它们的外键。 206 | 207 | 例如,为每条微博添加所在城市的信息,而城市单独作为一张表。 208 | 209 | ```java 210 | sqlite3.dropTable("tweets"); 211 | Tweet = sqlite3.createTable("tweets", "zombie_id int", "city_id int", "content text"); 212 | Table City = sqlite3.createTable("cities", "name text"); 213 | ``` 214 | 215 | 其中表`cities`包含所有城市的信息,`tweets`记录僵尸和城市的关联关系。`Zombie`为了自己去过的`City`,它首先要连接到表`tweets`,再通过它访问`cities`。 216 | 217 | ```java 218 | Zombie.hasMany("tweets").by("zombie_id"); // has defined above 219 | Zombie.hasAndBelongsToMany("travelled_cities").by("city_id").in("cities").through("tweets"); 220 | ``` 221 | 222 | 顾名思义,多对多的关联返回的类型一定是`Table`而不是`Record`。 223 | 224 | ### 关联总结 225 | 226 | * 一对一:有外键的表用`belongsTo`;无外键的表用`hasOne`。 227 | * 一对多:有外键的表用`belongsTo`;无外键的表用`hasMany`。 228 | * 多对多:两个多方都用`hasAndBelongsToMany`;映射表用`belongsTo`。 229 | 230 | 通过`through`可以任意组合其他关联。 231 | 232 | # 总结 233 | 234 | 本文通过一个微博系统的例子,介绍了`jActiveRecord`的常用功能。更多特性请访问本站[Wiki](https://github.com/redraiment/jactiverecord/wiki)。 235 | -------------------------------------------------------------------------------- /jactiverecord/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | 6 | me.zzp 7 | java-on-rails 8 | 1.0.0-SNAPSHOT 9 | 10 | 11 | jactiverecord 12 | 2.3 13 | jar 14 | 15 | jActiveRecord 16 | ActiveRecord of Ruby on Rails in Java 17 | 18 | 19 | 20 | org.junit.jupiter 21 | junit-jupiter 22 | test 23 | 24 | 25 | com.h2database 26 | h2 27 | test 28 | 29 | 30 | org.xerial 31 | sqlite-jdbc 32 | test 33 | 34 | 35 | org.postgresql 36 | postgresql 37 | test 38 | 39 | 40 | mysql 41 | mysql-connector-java 42 | test 43 | 44 | 45 | 46 | 47 | 48 | 49 | maven-source-plugin 50 | 51 | 52 | maven-javadoc-plugin 53 | 54 | 55 | maven-gpg-plugin 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /jactiverecord/src/main/java/me/zzp/ar/Association.java: -------------------------------------------------------------------------------- 1 | package me.zzp.ar; 2 | 3 | import java.util.Map; 4 | import me.zzp.ar.ex.UndefinedAssociationException; 5 | 6 | /** 7 | * 表之间的关联。 8 | * 9 | * @since 1.0 10 | * @author redraiment 11 | */ 12 | public final class Association { 13 | private final Map relations; 14 | private final boolean onlyOne; 15 | private final boolean ancestor; 16 | 17 | private Association assoc; 18 | String target; 19 | String key; 20 | 21 | Association(Map relations, String name, boolean onlyOne, boolean ancestor) { 22 | this.relations = relations; 23 | this.onlyOne = onlyOne; 24 | this.ancestor = ancestor; 25 | 26 | this.target = name; 27 | this.key = name.concat("_id"); 28 | this.assoc = null; 29 | } 30 | 31 | public boolean isOnlyOneResult() { 32 | return onlyOne; 33 | } 34 | 35 | public boolean isAncestor() { 36 | return ancestor; 37 | } 38 | 39 | public boolean isCross() { 40 | return assoc != null; 41 | } 42 | 43 | public Association by(String key) { 44 | this.key = key; 45 | return this; 46 | } 47 | 48 | public Association in(String table) { 49 | this.target = table; 50 | return this; 51 | } 52 | 53 | public Association through(String assoc) { 54 | assoc = DB.parseKeyParameter(assoc); 55 | if (relations.containsKey(assoc)) { 56 | this.assoc = relations.get(assoc); 57 | } else { 58 | throw new UndefinedAssociationException(assoc); 59 | } 60 | return this; 61 | } 62 | 63 | String assoc(String source, int id) { 64 | String template = isAncestor()? "%1$s on %2$s.%3$s = %1$s.id": "%1$s on %1$s.%3$s = %2$s.id"; 65 | if (isCross()) { 66 | return String.format(template, assoc.target, target, key).concat(" join ").concat(assoc.assoc(source, id)); 67 | } else { 68 | return String.format(template.concat(" and %1$s.id = %4$d"), source, target, key, id); 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /jactiverecord/src/main/java/me/zzp/ar/DB.java: -------------------------------------------------------------------------------- 1 | package me.zzp.ar; 2 | 3 | import java.sql.Connection; 4 | import java.sql.DatabaseMetaData; 5 | import java.sql.PreparedStatement; 6 | import java.sql.ResultSet; 7 | import java.sql.SQLException; 8 | import java.sql.Statement; 9 | import java.sql.Timestamp; 10 | import java.util.Arrays; 11 | import java.util.HashMap; 12 | import java.util.HashSet; 13 | import java.util.LinkedHashMap; 14 | import java.util.Map; 15 | import java.util.Properties; 16 | import java.util.ServiceLoader; 17 | import java.util.Set; 18 | import javax.sql.DataSource; 19 | import me.zzp.ar.d.Dialect; 20 | import me.zzp.ar.ex.DBOpenException; 21 | import me.zzp.ar.ex.IllegalTableNameException; 22 | import me.zzp.ar.ex.SqlExecuteException; 23 | import me.zzp.ar.ex.TransactionException; 24 | import me.zzp.ar.ex.UnsupportedDatabaseException; 25 | import me.zzp.ar.pool.SingletonDataSource; 26 | import me.zzp.util.Seq; 27 | 28 | /** 29 | * 数据库对象。 30 | * 31 | * @since 1.0 32 | * @author redraiment 33 | */ 34 | public final class DB { 35 | private static final ServiceLoader dialects; 36 | 37 | static { 38 | dialects = ServiceLoader.load(Dialect.class); 39 | } 40 | 41 | public static DB open(String url) { 42 | return open(url, new Properties()); 43 | } 44 | 45 | public static DB open(String url, String username, String password) { 46 | Properties info = new Properties(); 47 | info.put("user", username); 48 | info.put("password", password); 49 | return open(url, info); 50 | } 51 | 52 | public static DB open(String url, Properties info) { 53 | try { 54 | return open(new SingletonDataSource(url, info)); 55 | } catch (SQLException e) { 56 | throw new DBOpenException(e); 57 | } 58 | } 59 | 60 | public static DB open(DataSource pool) { 61 | try (Connection base = pool.getConnection()) { 62 | for (Dialect dialect : dialects) { 63 | if (dialect.accept(base)) { 64 | base.close(); 65 | return new DB(pool, dialect); 66 | } 67 | } 68 | 69 | DatabaseMetaData meta = base.getMetaData(); 70 | String version = String.format("%s %d.%d/%s", meta.getDatabaseProductName(), 71 | meta.getDatabaseMajorVersion(), 72 | meta.getDatabaseMinorVersion(), 73 | meta.getDatabaseProductVersion()); 74 | throw new UnsupportedDatabaseException(version); 75 | } catch (SQLException e) { 76 | throw new DBOpenException(e); 77 | } 78 | } 79 | 80 | private final DataSource pool; 81 | private final InheritableThreadLocal base; 82 | private final Dialect dialect; 83 | private final Map> columns; 84 | private final Map> relations; 85 | private final Map> hooks; 86 | 87 | private DB(DataSource pool, Dialect dialect) { 88 | this.pool = pool; 89 | this.base = new InheritableThreadLocal<>(); 90 | this.columns = new HashMap<>(); 91 | this.relations = new HashMap<>(); 92 | this.dialect = dialect; 93 | this.hooks = new HashMap<>(); 94 | } 95 | 96 | private Connection getConnection() { 97 | try { 98 | return base.get() == null? pool.getConnection(): base.get(); 99 | } catch (SQLException e) { 100 | throw new DBOpenException(e); 101 | } 102 | } 103 | 104 | void close(Connection c) { 105 | if (c != null && base.get() != c) { 106 | try { 107 | c.close(); 108 | } catch (SQLException e) { 109 | throw new RuntimeException("close Connection fail", e); 110 | } 111 | } 112 | } 113 | 114 | void close(Statement s) { 115 | if (s != null) { 116 | try { 117 | Connection c = s.getConnection(); 118 | s.close(); 119 | close(c); 120 | } catch (SQLException e) { 121 | throw new RuntimeException("close Statement fail", e); 122 | } 123 | } 124 | } 125 | 126 | void close(ResultSet rs) { 127 | if (rs != null) { 128 | try { 129 | Statement s = rs.getStatement(); 130 | rs.close(); 131 | close(s); 132 | } catch (SQLException e) { 133 | throw new RuntimeException("close ResultSet fail", e); 134 | } 135 | } 136 | } 137 | 138 | public Set getTableNames() { 139 | Set tables = new HashSet(); 140 | try (Connection c = pool.getConnection()) { 141 | DatabaseMetaData db = c.getMetaData(); 142 | try (ResultSet rs = db.getTables(null, null, "%", new String[] {"TABLE"})) { 143 | while (rs.next()) { 144 | tables.add(rs.getString("table_name")); 145 | } 146 | } 147 | } catch (SQLException e) { 148 | throw new DBOpenException(e); 149 | } 150 | return tables; 151 | } 152 | 153 | public Set getTables() { 154 | Set
tables = new HashSet(); 155 | for (String name : getTableNames()) { 156 | tables.add(active(name)); 157 | } 158 | return tables; 159 | } 160 | 161 | private Map getColumns(String name) throws SQLException { 162 | if (!columns.containsKey(name)) { 163 | synchronized (columns) { 164 | if (!columns.containsKey(name)) { 165 | String catalog, schema, table; 166 | String[] patterns = name.split("\\."); 167 | if (patterns.length == 1) { 168 | catalog = null; 169 | schema = null; 170 | table = patterns[0]; 171 | } else if (patterns.length == 2) { 172 | catalog = null; 173 | schema = patterns[0]; 174 | table = patterns[1]; 175 | } else if (patterns.length == 3) { 176 | catalog = patterns[0]; 177 | schema = patterns[1]; 178 | table = patterns[2]; 179 | } else { 180 | throw new IllegalArgumentException(String.format("Illegal table name: %s", name)); 181 | } 182 | 183 | Map column = new LinkedHashMap<>(); 184 | try (Connection c = pool.getConnection()) { 185 | DatabaseMetaData db = c.getMetaData(); 186 | try (ResultSet rs = db.getColumns(catalog, schema, table, null)) { 187 | while (rs.next()) { 188 | String columnName = rs.getString("column_name"); 189 | if (columnName.equalsIgnoreCase("id") 190 | || columnName.equalsIgnoreCase("created_at") 191 | || columnName.equalsIgnoreCase("updated_at")) { 192 | continue; 193 | } 194 | column.put(parseKeyParameter(columnName), rs.getInt("data_type")); 195 | } 196 | } 197 | } 198 | columns.put(name, column); 199 | } 200 | } 201 | } 202 | return columns.get(name); 203 | } 204 | 205 | public Table active(String name) { 206 | name = dialect.getCaseIdentifier(name); 207 | 208 | if (!relations.containsKey(name)) { 209 | synchronized (relations) { 210 | if (!relations.containsKey(name)) { 211 | relations.put(name, new HashMap()); 212 | } 213 | } 214 | } 215 | 216 | if (!hooks.containsKey(name)) { 217 | synchronized (hooks) { 218 | if (!hooks.containsKey(name)) { 219 | hooks.put(name, new HashMap()); 220 | } 221 | } 222 | } 223 | 224 | try { 225 | return new Table(this, name, getColumns(name), relations.get(name), hooks.get(name)); 226 | } catch (SQLException e) { 227 | throw new IllegalTableNameException(name, e); 228 | } 229 | } 230 | 231 | public PreparedStatement prepare(String sql, Object[] params, int[] types) { 232 | Connection c = getConnection(); 233 | try { 234 | PreparedStatement call; 235 | if (sql.trim().toLowerCase().startsWith("insert")) { 236 | call = c.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS); 237 | } else { 238 | call = c.prepareStatement(sql); 239 | } 240 | 241 | if (params != null && params.length > 0) { 242 | for (int i = 0; i < params.length; i++) { 243 | if (params[i] != null) { 244 | call.setObject(i + 1, params[i]); 245 | } else { 246 | call.setNull(i + 1, types[i]); 247 | } 248 | } 249 | } 250 | return call; 251 | } catch (SQLException e) { 252 | throw new SqlExecuteException(sql, e); 253 | } 254 | } 255 | 256 | public void execute(String sql, Object[] params, int[] types) { 257 | PreparedStatement call = prepare(sql, params, types); 258 | try { 259 | call.executeUpdate(); 260 | } catch (SQLException e) { 261 | throw new SqlExecuteException(sql, e); 262 | } finally { 263 | close(call); 264 | } 265 | } 266 | 267 | public void execute(String sql) { 268 | execute(sql, null, null); 269 | } 270 | 271 | public ResultSet query(String sql, Object... params) { 272 | try { 273 | PreparedStatement call = prepare(sql, params, null); 274 | return call.executeQuery(); 275 | } catch (SQLException e) { 276 | throw new SqlExecuteException(sql, e); 277 | } 278 | } 279 | 280 | public Table createTable(String name, String... columns) { 281 | String template = "create table %s (id %s, %s, created_at timestamp, updated_at timestamp)"; 282 | execute(String.format(template, name, dialect.getIdentity(), Seq.join(Arrays.asList(columns), ", "))); 283 | return active(name); 284 | } 285 | 286 | public void dropTable(String name) { 287 | execute(String.format("drop table if exists %s", name)); 288 | } 289 | 290 | public void createIndex(String name, String table, String... columns) { 291 | execute(String.format("create index %s on %s(%s)", name, table, Seq.join(Arrays.asList(columns), ", "))); 292 | } 293 | 294 | public void dropIndex(String name, String table) { 295 | execute(String.format("drop index %s", name)); 296 | } 297 | 298 | /* Transaction */ 299 | public void batch(Runnable transaction) { 300 | // TODO: 不支持嵌套事务 301 | try (Connection c = pool.getConnection()) { 302 | boolean commit = c.getAutoCommit(); 303 | try { 304 | c.setAutoCommit(false); 305 | } catch (SQLException e) { 306 | throw new TransactionException("transaction setAutoCommit(false)", e); 307 | } 308 | base.set(c); 309 | 310 | try { 311 | transaction.run(); 312 | } catch (RuntimeException e) { 313 | try { 314 | c.rollback(); 315 | c.setAutoCommit(commit); 316 | } catch (SQLException ex) { 317 | throw new TransactionException("transaction rollback: " + ex.getMessage(), e); 318 | } 319 | throw e; 320 | } 321 | 322 | try { 323 | c.commit(); 324 | } catch (SQLException e) { 325 | throw new TransactionException("transaction commit", e); 326 | } 327 | c.setAutoCommit(commit); 328 | } catch (SQLException e) { 329 | throw new DBOpenException(e); 330 | } finally { 331 | base.set(null); 332 | } 333 | } 334 | 335 | public boolean tx(Runnable transaction) { 336 | try { 337 | batch(transaction); 338 | } catch (Throwable e) { 339 | return false; 340 | } 341 | return true; 342 | } 343 | 344 | /* Utility */ 345 | public static Timestamp now() { 346 | return new Timestamp(System.currentTimeMillis()); 347 | } 348 | 349 | static String parseKeyParameter(String name) { 350 | name = name.toLowerCase(); 351 | if (name.endsWith(":")) { 352 | name = name.substring(0, name.length() - 1); 353 | } 354 | return name; 355 | } 356 | } 357 | -------------------------------------------------------------------------------- /jactiverecord/src/main/java/me/zzp/ar/Lambda.java: -------------------------------------------------------------------------------- 1 | package me.zzp.ar; 2 | 3 | import java.lang.reflect.InvocationTargetException; 4 | import java.lang.reflect.Method; 5 | import me.zzp.ar.ex.IllegalFieldNameException; 6 | 7 | final class Lambda { 8 | private final Object o; 9 | private final Method fn; 10 | 11 | Lambda(Object o, Method fn) { 12 | this.o = o; 13 | this.fn = fn; 14 | } 15 | 16 | Object call(Record record, Object value) { 17 | try { 18 | return fn.invoke(o, record, value); 19 | } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { 20 | throw new IllegalFieldNameException(fn.getName(), e); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /jactiverecord/src/main/java/me/zzp/ar/Query.java: -------------------------------------------------------------------------------- 1 | package me.zzp.ar; 2 | 3 | import java.util.List; 4 | import me.zzp.ar.sql.TSqlBuilder; 5 | 6 | /** 7 | * 高级查询对象。 8 | * 9 | * @since 2.0 10 | * @author redraiment 11 | */ 12 | public class Query { 13 | private final Table table; 14 | private final TSqlBuilder sql; 15 | 16 | Query(Table table) { 17 | this.table = table; 18 | this.sql = new TSqlBuilder(); 19 | } 20 | 21 | public List all(Object... params) { 22 | return table.query(sql, params); 23 | } 24 | 25 | public Record one(Object... params) { 26 | limit(1); 27 | List models = all(params); 28 | if (models == null || models.isEmpty()) { 29 | return null; 30 | } else { 31 | return models.get(0); 32 | } 33 | } 34 | 35 | Query select(String... columns) { 36 | sql.select(columns); 37 | return this; 38 | } 39 | 40 | Query from(String table) { 41 | sql.from(table); 42 | return this; 43 | } 44 | 45 | Query join(String table) { 46 | sql.join(table); 47 | return this; 48 | } 49 | 50 | public Query where(String condition) { 51 | sql.addCondition(condition); 52 | return this; 53 | } 54 | 55 | public Query groupBy(String... columns) { 56 | sql.groupBy(columns); 57 | return this; 58 | } 59 | 60 | public Query having(String... conditions) { 61 | sql.having(conditions); 62 | return this; 63 | } 64 | 65 | public Query orderBy(String... columns) { 66 | sql.orderBy(columns); 67 | return this; 68 | } 69 | 70 | public Query limit(int limit) { 71 | sql.limit(limit); 72 | return this; 73 | } 74 | 75 | public Query offset(int offset) { 76 | sql.offset(offset); 77 | return this; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /jactiverecord/src/main/java/me/zzp/ar/Record.java: -------------------------------------------------------------------------------- 1 | package me.zzp.ar; 2 | 3 | import java.util.Map; 4 | import java.util.Set; 5 | 6 | /** 7 | * 行记录对象。 8 | * 9 | * @since 1.0 10 | * @author redraiment 11 | */ 12 | public final class Record { 13 | private final Table table; 14 | private final Map values; 15 | 16 | Record(Table table, Map values) { 17 | this.table = table; 18 | this.values = values; 19 | } 20 | 21 | public Set columnNames() { 22 | return values.keySet(); 23 | } 24 | 25 | public E get(String name) { 26 | name = DB.parseKeyParameter(name); 27 | Object value = null; 28 | 29 | if (values.containsKey(name)) { 30 | value = values.get(name); 31 | } else if (table.relations.containsKey(name)) { 32 | Association relation = table.relations.get(name); 33 | Table active = table.dbo.active(relation.target); 34 | active.join(relation.assoc(table.name, getInt("id"))); 35 | if (relation.isAncestor() && !relation.isCross()) { 36 | active.constrain(relation.key, getInt("id")); 37 | } 38 | value = (relation.isOnlyOneResult()? active.first(): active); 39 | } 40 | 41 | String key = "get_".concat(name); 42 | if (table.hooks.containsKey(key)) { 43 | value = table.hooks.get(key).call(this, value); 44 | } 45 | return (E)value; 46 | } 47 | 48 | /* For primitive types */ 49 | public boolean getBool(String name) { 50 | return get(name); 51 | } 52 | 53 | public byte getByte(String name) { 54 | return get(name); 55 | } 56 | 57 | public char getChar(String name) { 58 | return get(name); 59 | } 60 | 61 | public short getShort(String name) { 62 | return get(name); 63 | } 64 | 65 | public int getInt(String name) { 66 | return get(name); 67 | } 68 | 69 | public long getLong(String name) { 70 | return get(name); 71 | } 72 | 73 | public float getFloat(String name) { 74 | return get(name); 75 | } 76 | 77 | public double getDouble(String name) { 78 | return get(name); 79 | } 80 | 81 | /* For any other types */ 82 | 83 | public String getStr(String name) { 84 | return get(name); 85 | } 86 | 87 | public E get(String name, Class type) { 88 | return type.cast(get(name)); 89 | } 90 | 91 | public Record set(String name, Object value) { 92 | name = DB.parseKeyParameter(name); 93 | String key = "set_".concat(name); 94 | if (table.hooks.containsKey(key)) { 95 | value = table.hooks.get(key).call(this, value); 96 | } 97 | values.put(name, value); 98 | return this; 99 | } 100 | 101 | public Record save() { 102 | table.update(this); 103 | return this; 104 | } 105 | 106 | public Record update(Object... args) { 107 | for (int i = 0; i < args.length; i += 2) { 108 | set(args[i].toString(), args[i + 1]); 109 | } 110 | return save(); 111 | } 112 | 113 | public void destroy() { 114 | table.delete(this); 115 | } 116 | 117 | @Override 118 | public String toString() { 119 | StringBuilder line = new StringBuilder(); 120 | for (Map.Entry e : values.entrySet()) { 121 | line.append(String.format("%s = %s\n", e.getKey(), e.getValue())); 122 | } 123 | return line.toString(); 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /jactiverecord/src/main/java/me/zzp/ar/Table.java: -------------------------------------------------------------------------------- 1 | package me.zzp.ar; 2 | 3 | import java.lang.reflect.Method; 4 | import java.sql.PreparedStatement; 5 | import java.sql.ResultSet; 6 | import java.sql.ResultSetMetaData; 7 | import java.sql.SQLException; 8 | import java.sql.Types; 9 | import java.util.ArrayList; 10 | import java.util.Collections; 11 | import java.util.HashMap; 12 | import java.util.LinkedHashMap; 13 | import java.util.LinkedList; 14 | import java.util.List; 15 | import java.util.Map; 16 | import me.zzp.ar.ex.IllegalFieldNameException; 17 | import me.zzp.ar.ex.SqlExecuteException; 18 | import me.zzp.ar.sql.SqlBuilder; 19 | import me.zzp.ar.sql.TSqlBuilder; 20 | import me.zzp.util.Seq; 21 | 22 | /** 23 | * 表对象。 24 | * 25 | * @since 1.0 26 | * @author redraiment 27 | */ 28 | public final class Table { 29 | final DB dbo; 30 | final String name; 31 | final Map columns; 32 | final Map relations; 33 | final Map hooks; 34 | final String primaryKey; 35 | 36 | private String foreignTable; 37 | private final Map foreignKeys = new HashMap<>(); 38 | 39 | Table(DB dbo, String name, Map columns, Map relations, Map hooks) { 40 | this.dbo = dbo; 41 | this.name = name; 42 | this.columns = columns; 43 | this.relations = relations; 44 | this.hooks = hooks; 45 | this.primaryKey = name.concat(".id"); 46 | } 47 | 48 | /** 49 | * 继承给定的JavaBean,扩展Record对象的get和set方法。 50 | * 51 | * @param bean 希望被继承的JavaBean 52 | * @return 返回Table自身 53 | * @since 2.3 54 | */ 55 | public Table extend(Object bean) { 56 | Class type = bean.getClass(); 57 | for (Method method : type.getDeclaredMethods()) { 58 | Class returnType = method.getReturnType(); 59 | Class[] params = method.getParameterTypes(); 60 | String key = method.getName(); 61 | 62 | if (params.length == 2 63 | && key.length() > 3 64 | && (key.startsWith("get") || key.startsWith("set")) 65 | && params[0].isAssignableFrom(Record.class) 66 | && params[1].isAssignableFrom(Object.class) 67 | && Object.class.isAssignableFrom(returnType)) { 68 | key = key.replaceAll("(?=[A-Z])", "_").toLowerCase(); 69 | hooks.put(key, new Lambda(bean, method)); 70 | } 71 | } 72 | 73 | return this; 74 | } 75 | 76 | public Map getColumns() { 77 | return Collections.unmodifiableMap(columns); 78 | } 79 | 80 | /* Association */ 81 | private Association assoc(String name, boolean onlyOne, boolean ancestor) { 82 | name = DB.parseKeyParameter(name); 83 | Association assoc = new Association(relations, name, onlyOne, ancestor); 84 | relations.put(name, assoc); 85 | return assoc; 86 | } 87 | 88 | public Association belongsTo(String name) { 89 | return assoc(name, true, false); 90 | } 91 | 92 | public Association hasOne(String name) { 93 | return assoc(name, true, true); 94 | } 95 | 96 | public Association hasMany(String name) { 97 | return assoc(name, false, true); 98 | } 99 | 100 | public Association hasAndBelongsToMany(String name) { 101 | return assoc(name, false, false); 102 | } 103 | 104 | private String[] getForeignKeys() { 105 | List conditions = new ArrayList<>(); 106 | for (Map.Entry e : foreignKeys.entrySet()) { 107 | conditions.add(String.format("%s.%s = %d", name, e.getKey(), e.getValue())); 108 | } 109 | return conditions.toArray(new String[0]); 110 | } 111 | 112 | public Table constrain(String key, int id) { 113 | foreignKeys.put(DB.parseKeyParameter(key), id); 114 | return this; 115 | } 116 | 117 | public Table join(String table) { 118 | this.foreignTable = table; 119 | return this; 120 | } 121 | 122 | /* CRUD */ 123 | public Record create(Object... args) { 124 | Map data = new HashMap<>(); 125 | data.putAll(foreignKeys); 126 | for (int i = 0; i < args.length; i += 2) { 127 | String key = DB.parseKeyParameter(args[i].toString()); 128 | if (!columns.containsKey(key)) { 129 | throw new IllegalFieldNameException(key); 130 | } 131 | Object value = args[i + 1]; 132 | data.put(key, value); 133 | } 134 | 135 | String[] fields = new String[data.size() + 2]; 136 | int[] types = new int[data.size() + 2]; 137 | Object[] values = new Object[data.size() + 2]; 138 | int index = 0; 139 | for (Map.Entry e : data.entrySet()) { 140 | fields[index] = e.getKey(); 141 | types[index] = columns.get(e.getKey()); 142 | values[index] = e.getValue(); 143 | index++; 144 | } 145 | Seq.assignAt(fields, Seq.array(-2, -1), "created_at", "updated_at"); 146 | Seq.assignAt(types, Seq.array(-2, -1), Types.TIMESTAMP, Types.TIMESTAMP); 147 | Seq.assignAt(values, Seq.array(-2, -1), DB.now(), DB.now()); 148 | 149 | SqlBuilder sql = new TSqlBuilder(); 150 | sql.insert().into(name).values(fields); 151 | PreparedStatement call = dbo.prepare(sql.toString(), values, types); 152 | try { 153 | int id = 0; 154 | if (call.executeUpdate() > 0) { 155 | ResultSet rs = call.getGeneratedKeys(); 156 | if (rs != null && rs.next()) { 157 | id = rs.getInt(1); 158 | rs.close(); 159 | } 160 | } 161 | return id > 0 ? find(id) : null; 162 | } catch (SQLException e) { 163 | throw new SqlExecuteException(sql.toString(), e); 164 | } finally { 165 | dbo.close(call); 166 | } 167 | } 168 | 169 | /** 170 | * 根据现有的Record创建新的Record. 171 | * 为跨数据库之间导数据提供便捷接口;同时也方便根据模板创建多条相似的纪录。 172 | * @param o Record对象 173 | * @return 根据参数创建的新的Record对象 174 | */ 175 | public Record create(Record o) { 176 | List params = new LinkedList<>(); 177 | for (String key : columns.keySet()) { 178 | if (!foreignKeys.containsKey(key)) { 179 | params.add(key); 180 | params.add(o.get(key)); 181 | } 182 | } 183 | return create(params.toArray()); 184 | } 185 | 186 | public void update(Record record) { 187 | String[] fields = new String[columns.size() + 1]; 188 | int[] types = new int[columns.size() + 1]; 189 | Object[] values = new Object[columns.size() + 1]; 190 | int index = 0; 191 | for (String column : columns.keySet()) { 192 | fields[index] = column; 193 | types[index] = columns.get(column); 194 | values[index] = record.get(column); 195 | index++; 196 | } 197 | 198 | fields[columns.size()] = "updated_at"; 199 | types[columns.size()] = Types.TIMESTAMP; 200 | values[columns.size()] = DB.now(); 201 | 202 | SqlBuilder sql = new TSqlBuilder(); 203 | sql.update(name).set(fields).where(String.format("%s = %d", primaryKey, record.getInt("id"))); 204 | dbo.execute(sql.toString(), values, types); 205 | } 206 | 207 | public void delete(Record record) { 208 | int id = record.get("id"); 209 | SqlBuilder sql = new TSqlBuilder(); 210 | sql.delete().from(name).where(String.format("%s = %d", primaryKey, id)); 211 | dbo.execute(sql.toString()); 212 | } 213 | 214 | public void purge() { 215 | // TODO: need enhancement 216 | for (Record record : all()) { 217 | delete(record); 218 | } 219 | } 220 | 221 | List query(SqlBuilder sql, Object... args) { 222 | List records = new LinkedList<>(); 223 | ResultSet rs = dbo.query(sql.toString(), args); 224 | try { 225 | ResultSetMetaData meta = rs.getMetaData(); 226 | while (rs.next()) { 227 | Map values = new LinkedHashMap<>(); 228 | for (int i = 1; i <= meta.getColumnCount(); i++) { 229 | String label = DB.parseKeyParameter(meta.getColumnLabel(i)); 230 | values.put(label, rs.getObject(label)); 231 | } 232 | records.add(new Record(this, values)); 233 | } 234 | } catch (SQLException e) { 235 | throw new SqlExecuteException(sql.toString(), e); 236 | } finally { 237 | dbo.close(rs); 238 | } 239 | return records; 240 | } 241 | 242 | public Query select(String... columns) { 243 | Query sql = new Query(this); 244 | if (columns == null || columns.length == 0) { 245 | sql.select(String.format("%s.*", name)); 246 | } else { 247 | sql.select(columns); 248 | } 249 | sql.from(name); 250 | if (foreignTable != null && !foreignTable.isEmpty()) { 251 | sql.join(foreignTable); 252 | } 253 | if (!foreignKeys.isEmpty()) { 254 | for (String condition : getForeignKeys()) { 255 | sql.where(condition); 256 | } 257 | } 258 | return sql.orderBy(primaryKey); 259 | } 260 | 261 | public Record first() { 262 | return select().limit(1).one(); 263 | } 264 | 265 | public Record first(String condition, Object... args) { 266 | return select().where(condition).limit(1).one(args); 267 | } 268 | 269 | public Record last() { 270 | return select().orderBy(primaryKey.concat(" desc")).limit(1).one(); 271 | } 272 | 273 | public Record last(String condition, Object... args) { 274 | return select().where(condition).orderBy(primaryKey.concat(" desc")).limit(1).one(args); 275 | } 276 | 277 | public Record find(int id) { 278 | return first(primaryKey.concat(" = ?"), id); 279 | } 280 | 281 | /** 282 | * 根据指定列,返回符合条件的第一条记录. 283 | * @param key 要匹配的列名 284 | * @param value 要匹配的值 285 | * @return 返回符合条件的第一条记录 286 | */ 287 | public Record findA(String key, Object value) { 288 | key = DB.parseKeyParameter(key); 289 | if (value != null) { 290 | return first(key.concat(" = ?"), value); 291 | } else { 292 | return first(key.concat(" is null")); 293 | } 294 | } 295 | 296 | public List findBy(String key, Object value) { 297 | key = DB.parseKeyParameter(key); 298 | if (value != null) { 299 | return where(key.concat(" = ?"), value); 300 | } else { 301 | return where(key.concat(" is null")); 302 | } 303 | } 304 | 305 | public List all() { 306 | return select().all(); 307 | } 308 | 309 | public List where(String condition, Object... args) { 310 | return select().where(condition).all(args); 311 | } 312 | 313 | public List paging(int page, int size) { 314 | return select().limit(size).offset(page * size).all(); 315 | } 316 | } 317 | -------------------------------------------------------------------------------- /jactiverecord/src/main/java/me/zzp/ar/d/Dialect.java: -------------------------------------------------------------------------------- 1 | package me.zzp.ar.d; 2 | 3 | import java.sql.Connection; 4 | 5 | /** 6 | * SQL方言接口。 7 | * 8 | * @since 1.0 9 | * @author redraiment 10 | */ 11 | public interface Dialect { 12 | /** 13 | * 判断给定的数据库连接是否使用当前方言。 14 | * 15 | * @param c 数据库连接 16 | * @return 如果该连接属于当前方言,返回true;否则返回false。 17 | */ 18 | public boolean accept(Connection c); 19 | 20 | /** 21 | * 返回当前方言定义自增的整数类型主键的方法。 22 | * 23 | * @return 返回当前方言定义自增的整数类型主键的方法。 24 | */ 25 | public String getIdentity(); 26 | 27 | /** 28 | * 将给定的标识转换成当前数据库内部的大小写形式。 29 | * 30 | * @param identifier 需要转换的标识。 31 | * @return 转换后的标识。 32 | */ 33 | public String getCaseIdentifier(String identifier); 34 | } 35 | -------------------------------------------------------------------------------- /jactiverecord/src/main/java/me/zzp/ar/d/HyperSQLDialect.java: -------------------------------------------------------------------------------- 1 | package me.zzp.ar.d; 2 | 3 | import java.sql.Connection; 4 | import java.sql.DatabaseMetaData; 5 | import java.sql.SQLException; 6 | import me.zzp.ar.ex.DBOpenException; 7 | 8 | /** 9 | * HyperSQL方言。 10 | * 11 | * @since 1.0 12 | * @author redraiment 13 | */ 14 | public class HyperSQLDialect implements Dialect { 15 | /** 16 | * 判断当前数据库的名称里是否包含hsql(忽略大小写)。 17 | * 18 | * @param c 数据库连接 19 | * @return 如果数据库名称包含hsql,则返回true;否则返回false。 20 | */ 21 | @Override 22 | public boolean accept(Connection c) { 23 | try { 24 | DatabaseMetaData d = c.getMetaData(); 25 | String name = d.getDatabaseProductName(); // HSQL Database Engine 26 | return name.toLowerCase().contains("hsql"); 27 | } catch (SQLException e) { 28 | throw new DBOpenException(e); 29 | } 30 | } 31 | 32 | /** 33 | * 返回HyperSQL中定义自增长的整型主键语句。 34 | * 35 | * @return integer primary key generated always as identity (start with 1, increment by 1)。 36 | */ 37 | @Override 38 | public String getIdentity() { 39 | return "integer primary key generated always as identity (start with 1, increment by 1)"; 40 | } 41 | 42 | /** 43 | * 将标识转换成为大写。 44 | * 45 | * @param identifier 待转换的标识。 46 | * @return 大写形式的标识。 47 | */ 48 | @Override 49 | public String getCaseIdentifier(String identifier) { 50 | return identifier.toUpperCase(); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /jactiverecord/src/main/java/me/zzp/ar/d/MySQLDialect.java: -------------------------------------------------------------------------------- 1 | package me.zzp.ar.d; 2 | 3 | import java.sql.Connection; 4 | import java.sql.DatabaseMetaData; 5 | import java.sql.SQLException; 6 | import me.zzp.ar.ex.DBOpenException; 7 | 8 | /** 9 | * MySQL方言。 10 | * 11 | * @since 1.0 12 | * @author redraiment 13 | */ 14 | public class MySQLDialect implements Dialect { 15 | /** 16 | * 判断当前数据库的名称里是否包含mysql(忽略大小写)。 17 | * 18 | * @param c 数据库连接 19 | * @return 如果数据库名称包含mysql,则返回true;否则返回false。 20 | */ 21 | @Override 22 | public boolean accept(Connection c) { 23 | try { 24 | DatabaseMetaData d = c.getMetaData(); 25 | String name = d.getDatabaseProductName(); // MySQL 26 | return name.toLowerCase().contains("mysql"); 27 | } catch (SQLException e) { 28 | throw new DBOpenException(e); 29 | } 30 | } 31 | 32 | /** 33 | * 返回MySQL中定义自增长的整型主键语句。 34 | * 35 | * @return integer primary key auto_increment。 36 | */ 37 | @Override 38 | public String getIdentity() { 39 | return "integer primary key auto_increment"; 40 | } 41 | 42 | /** 43 | * 原样返回标识。 44 | * 45 | * @param identifier 待转换的标识。 46 | * @return 标识本身。 47 | */ 48 | @Override 49 | public String getCaseIdentifier(String identifier) { 50 | return identifier; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /jactiverecord/src/main/java/me/zzp/ar/d/PostgreSQLDialect.java: -------------------------------------------------------------------------------- 1 | package me.zzp.ar.d; 2 | 3 | import java.sql.Connection; 4 | import java.sql.DatabaseMetaData; 5 | import java.sql.SQLException; 6 | import me.zzp.ar.ex.DBOpenException; 7 | 8 | /** 9 | * PostgreSQL方言。 10 | * 11 | * @since 1.0 12 | * @author redraiment 13 | */ 14 | public class PostgreSQLDialect implements Dialect { 15 | /** 16 | * 判断当前数据库的名称里是否包含postgresql(忽略大小写)。 17 | * 18 | * @param c 数据库连接 19 | * @return 如果数据库名称包含postgresql,则返回true;否则返回false。 20 | */ 21 | @Override 22 | public boolean accept(Connection c) { 23 | try { 24 | DatabaseMetaData d = c.getMetaData(); 25 | String name = d.getDatabaseProductName(); // PostgreSQL 26 | return name.toLowerCase().contains("postgresql"); 27 | } catch (SQLException e) { 28 | throw new DBOpenException(e); 29 | } 30 | } 31 | 32 | /** 33 | * 返回PostgreSQL中定义自增长的整型主键语句。 34 | * 35 | * @return serial primary key。 36 | */ 37 | @Override 38 | public String getIdentity() { 39 | return "serial primary key"; 40 | } 41 | 42 | /** 43 | * 将标识转换成为小写。 44 | * 45 | * @param identifier 待转换的标识。 46 | * @return 小写形式的标识。 47 | */ 48 | @Override 49 | public String getCaseIdentifier(String identifier) { 50 | return identifier.toLowerCase(); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /jactiverecord/src/main/java/me/zzp/ar/d/SQLiteDialect.java: -------------------------------------------------------------------------------- 1 | package me.zzp.ar.d; 2 | 3 | import java.sql.Connection; 4 | import java.sql.DatabaseMetaData; 5 | import java.sql.SQLException; 6 | import me.zzp.ar.ex.DBOpenException; 7 | 8 | /** 9 | * SQLite方言。 10 | * 11 | * @since 1.0 12 | * @author redraiment 13 | */ 14 | public class SQLiteDialect implements Dialect { 15 | /** 16 | * 判断当前数据库的名称里是否包含sqlite(忽略大小写)。 17 | * 18 | * @param c 数据库连接 19 | * @return 如果数据库名称包含sqlite,则返回true;否则返回false。 20 | */ 21 | @Override 22 | public boolean accept(Connection c) { 23 | try { 24 | DatabaseMetaData d = c.getMetaData(); 25 | String name = d.getDatabaseProductName(); // SQLite 26 | return name.toLowerCase().contains("sqlite"); 27 | } catch (SQLException e) { 28 | throw new DBOpenException(e); 29 | } 30 | } 31 | 32 | /** 33 | * 返回SQLite中定义自增长的整型主键语句。 34 | * 35 | * @return integer primary key autoincrement。 36 | */ 37 | @Override 38 | public String getIdentity() { 39 | return "integer primary key autoincrement"; 40 | } 41 | 42 | /** 43 | * 原样返回标识。 44 | * 45 | * @param identifier 待转换的标识。 46 | * @return 标识本身。 47 | */ 48 | @Override 49 | public String getCaseIdentifier(String identifier) { 50 | return identifier; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /jactiverecord/src/main/java/me/zzp/ar/d/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright 2014 redraiment. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | 25 | /** 26 | *

该包提供不同数据库实现的特殊SQL方言。

27 | *

方言采用ServiceLoader机制自动加载,因此需要Java 6及以上版本支持。

28 | *

如果用户需要支持除 SQLite3、HyperSQL、MySQL以及PostgreSQL外的其他数据库, 29 | * 可自行实现me.zzp.ar.d.Dialect,并添加到META-INF/services/me.zzp.ar.d.Dialect 30 | * 文件中。

31 | * 32 | * @since 1.0 33 | */ 34 | package me.zzp.ar.d; -------------------------------------------------------------------------------- /jactiverecord/src/main/java/me/zzp/ar/ex/DBOpenException.java: -------------------------------------------------------------------------------- 1 | package me.zzp.ar.ex; 2 | 3 | /** 4 | * 连接数据库时遇到任何问题抛出此异常。 5 | * 6 | * @since 1.0 7 | * @author redraiment 8 | */ 9 | public class DBOpenException extends RuntimeException { 10 | public DBOpenException(Throwable cause) { 11 | super(cause); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /jactiverecord/src/main/java/me/zzp/ar/ex/IllegalFieldNameException.java: -------------------------------------------------------------------------------- 1 | package me.zzp.ar.ex; 2 | 3 | /** 4 | * 列不存在。 5 | * 6 | * @since 1.0 7 | * @author redraiment 8 | */ 9 | public class IllegalFieldNameException extends RuntimeException { 10 | public IllegalFieldNameException(String fieldName) { 11 | super(String.format("illegal field %s", fieldName)); 12 | } 13 | 14 | public IllegalFieldNameException(String fieldName, Throwable cause) { 15 | super(String.format("illegal field %s", fieldName), cause); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /jactiverecord/src/main/java/me/zzp/ar/ex/IllegalTableNameException.java: -------------------------------------------------------------------------------- 1 | package me.zzp.ar.ex; 2 | 3 | /** 4 | * 表不存在。 5 | * 6 | * @since 1.0 7 | * @author redraiment 8 | */ 9 | public class IllegalTableNameException extends RuntimeException { 10 | public IllegalTableNameException(String tableName, Throwable e) { 11 | super(String.format("illegal table %s", tableName), e); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /jactiverecord/src/main/java/me/zzp/ar/ex/SqlExecuteException.java: -------------------------------------------------------------------------------- 1 | package me.zzp.ar.ex; 2 | 3 | /** 4 | * 执行SQL时遇到任何问题抛出此异常。 5 | * 6 | * @since 1.0 7 | * @author redraiment 8 | */ 9 | public class SqlExecuteException extends RuntimeException { 10 | public SqlExecuteException(String sql, Throwable cause) { 11 | super(sql, cause); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /jactiverecord/src/main/java/me/zzp/ar/ex/TransactionException.java: -------------------------------------------------------------------------------- 1 | package me.zzp.ar.ex; 2 | 3 | /** 4 | * 在处理事务时遇到任何异常抛出此异常。 5 | * 6 | * @since 2.0 7 | * @author redraiment 8 | */ 9 | public class TransactionException extends RuntimeException { 10 | public TransactionException(String message, Throwable cause) { 11 | super(message, cause); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /jactiverecord/src/main/java/me/zzp/ar/ex/UndefinedAssociationException.java: -------------------------------------------------------------------------------- 1 | package me.zzp.ar.ex; 2 | 3 | /** 4 | * 遇到未定义的关联时抛出此异常。 5 | * 6 | * @since 1.0 7 | * @author redraiment 8 | */ 9 | public class UndefinedAssociationException extends RuntimeException { 10 | public UndefinedAssociationException(String name) { 11 | super(String.format("undefined association name: %s", name)); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /jactiverecord/src/main/java/me/zzp/ar/ex/UnsupportedDatabaseException.java: -------------------------------------------------------------------------------- 1 | package me.zzp.ar.ex; 2 | 3 | /** 4 | * 未找到相应的方言。 5 | * 6 | * @since 1.0 7 | * @author redraiment 8 | */ 9 | public class UnsupportedDatabaseException extends RuntimeException { 10 | public UnsupportedDatabaseException(String product) { 11 | super(String.format("Unsupported Database: %s", product)); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /jactiverecord/src/main/java/me/zzp/ar/ex/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright 2014 redraiment. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | 25 | /** 26 | *

该包统一管理所有异常对象。

27 | *

jActiveRecord所有的异常均为RuntimeException, 28 | * 避免了冗余的try {} catch () {}代码。

29 | */ 30 | package me.zzp.ar.ex; 31 | -------------------------------------------------------------------------------- /jactiverecord/src/main/java/me/zzp/ar/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright 2014 redraiment. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | 25 | /** 26 | * jActiveRecord是Java的ORM框架,灵感来自Ruby on Rails的ActiveRecord。 27 | */ 28 | package me.zzp.ar; 29 | -------------------------------------------------------------------------------- /jactiverecord/src/main/java/me/zzp/ar/pool/JdbcDataSource.java: -------------------------------------------------------------------------------- 1 | package me.zzp.ar.pool; 2 | 3 | import java.io.PrintWriter; 4 | import java.sql.Connection; 5 | import java.sql.DriverManager; 6 | import java.sql.SQLException; 7 | import java.sql.SQLFeatureNotSupportedException; 8 | import java.util.Properties; 9 | import java.util.logging.Logger; 10 | import javax.sql.DataSource; 11 | 12 | /** 13 | *

jActiveRecord默认DataSource实现,每一次均创建一个新的数据库连接。

14 | * 15 | * @since 2.0 16 | * @author redraiment 17 | */ 18 | public final class JdbcDataSource implements DataSource { 19 | private final String url; 20 | private final Properties info; 21 | 22 | public JdbcDataSource(String url, String username, String password) { 23 | this.url = url; 24 | info = new Properties(); 25 | info.put("user", username); 26 | info.put("password", password); 27 | } 28 | 29 | /** 30 | * 提供连接数据库基本信息。 31 | * 32 | * @param url 数据库连接地址 33 | * @param info 包含用户名、密码等登入信息。 34 | */ 35 | public JdbcDataSource(String url, Properties info) { 36 | this.url = url; 37 | this.info = info; 38 | } 39 | 40 | /** 41 | * 每次调用时均返回一个新的数据库连接。 42 | * 43 | * @return 一个新的数据库连接。 44 | * @throws SQLException 连接数据库失败。 45 | */ 46 | @Override 47 | public Connection getConnection() throws SQLException { 48 | return DriverManager.getConnection(url, info); 49 | } 50 | 51 | /** 52 | * 每次调用时均返回一个新的数据库连接。 53 | * 54 | * @return 一个新的数据库连接。 55 | * @throws SQLException 连接数据库失败。 56 | */ 57 | @Override 58 | public Connection getConnection(String username, String password) throws SQLException { 59 | return DriverManager.getConnection(url, username, password); 60 | } 61 | 62 | /** 63 | * 不支持,永远不会被调用。 64 | * 65 | * @return 无 66 | * @throws SQLException 从不 67 | */ 68 | @Override 69 | public PrintWriter getLogWriter() throws SQLException { 70 | throw new UnsupportedOperationException("Not supported yet."); 71 | } 72 | 73 | /** 74 | * 不支持,永远不会被调用。 75 | * 76 | * @throws SQLException 从不 77 | */ 78 | @Override 79 | public void setLogWriter(PrintWriter out) throws SQLException { 80 | throw new UnsupportedOperationException("Not supported yet."); 81 | } 82 | 83 | /** 84 | * 不支持,永远不会被调用。 85 | * 86 | * @throws SQLException 从不 87 | */ 88 | @Override 89 | public void setLoginTimeout(int seconds) throws SQLException { 90 | throw new UnsupportedOperationException("Not supported yet."); 91 | } 92 | 93 | /** 94 | * 不支持,永远不会被调用。 95 | * 96 | * @return 无 97 | * @throws SQLException 从不 98 | */ 99 | @Override 100 | public int getLoginTimeout() throws SQLException { 101 | throw new UnsupportedOperationException("Not supported yet."); 102 | } 103 | 104 | /** 105 | * 不支持,永远不会被调用。 106 | * 107 | * @return 无 108 | * @throws SQLFeatureNotSupportedException 从不 109 | */ 110 | @Override 111 | public Logger getParentLogger() throws SQLFeatureNotSupportedException { 112 | throw new UnsupportedOperationException("Not supported yet."); 113 | } 114 | 115 | /** 116 | * 不支持,永远不会被调用。 117 | * 118 | * @param 类型 119 | * @return 无 120 | * @throws SQLException 从不 121 | */ 122 | @Override 123 | public T unwrap(Class iface) throws SQLException { 124 | throw new UnsupportedOperationException("Not supported yet."); 125 | } 126 | 127 | /** 128 | * 不支持,永远不会被调用。 129 | * 130 | * @return 无 131 | * @throws SQLException 从不 132 | */ 133 | @Override 134 | public boolean isWrapperFor(Class iface) throws SQLException { 135 | throw new UnsupportedOperationException("Not supported yet."); 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /jactiverecord/src/main/java/me/zzp/ar/pool/SingletonConnection.java: -------------------------------------------------------------------------------- 1 | package me.zzp.ar.pool; 2 | 3 | import java.sql.Array; 4 | import java.sql.Blob; 5 | import java.sql.CallableStatement; 6 | import java.sql.Clob; 7 | import java.sql.Connection; 8 | import java.sql.DatabaseMetaData; 9 | import java.sql.NClob; 10 | import java.sql.PreparedStatement; 11 | import java.sql.SQLClientInfoException; 12 | import java.sql.SQLException; 13 | import java.sql.SQLWarning; 14 | import java.sql.SQLXML; 15 | import java.sql.Savepoint; 16 | import java.sql.Statement; 17 | import java.sql.Struct; 18 | import java.util.Map; 19 | import java.util.Properties; 20 | import java.util.concurrent.Executor; 21 | 22 | /** 23 | * 24 | * @author redraiment 25 | * @since 2.2 26 | * @see SingleConnectionDataSource 27 | */ 28 | final class SingletonConnection implements Connection { 29 | private final Connection c; 30 | 31 | SingletonConnection(Connection c) { 32 | this.c = c; 33 | } 34 | 35 | @Override 36 | public Statement createStatement() throws SQLException { 37 | return c.createStatement(); 38 | } 39 | 40 | @Override 41 | public PreparedStatement prepareStatement(String sql) throws SQLException { 42 | return new SingletonPreparedStatement(this, c.prepareStatement(sql)); 43 | } 44 | 45 | @Override 46 | public CallableStatement prepareCall(String sql) throws SQLException { 47 | return c.prepareCall(sql); 48 | } 49 | 50 | @Override 51 | public String nativeSQL(String sql) throws SQLException { 52 | return c.nativeSQL(sql); 53 | } 54 | 55 | @Override 56 | public void setAutoCommit(boolean autoCommit) throws SQLException { 57 | c.setAutoCommit(autoCommit); 58 | } 59 | 60 | @Override 61 | public boolean getAutoCommit() throws SQLException { 62 | return c.getAutoCommit(); 63 | } 64 | 65 | @Override 66 | public void commit() throws SQLException { 67 | c.commit(); 68 | } 69 | 70 | @Override 71 | public void rollback() throws SQLException { 72 | c.rollback(); 73 | } 74 | 75 | @Override 76 | public void close() throws SQLException { 77 | } 78 | 79 | @Override 80 | public boolean isClosed() throws SQLException { 81 | return c.isClosed(); 82 | } 83 | 84 | @Override 85 | public DatabaseMetaData getMetaData() throws SQLException { 86 | return c.getMetaData(); 87 | } 88 | 89 | @Override 90 | public void setReadOnly(boolean readOnly) throws SQLException { 91 | c.setReadOnly(readOnly); 92 | } 93 | 94 | @Override 95 | public boolean isReadOnly() throws SQLException { 96 | return c.isReadOnly(); 97 | } 98 | 99 | @Override 100 | public void setCatalog(String catalog) throws SQLException { 101 | c.setCatalog(catalog); 102 | } 103 | 104 | @Override 105 | public String getCatalog() throws SQLException { 106 | return c.getCatalog(); 107 | } 108 | 109 | @Override 110 | public void setTransactionIsolation(int level) throws SQLException { 111 | c.setTransactionIsolation(level); 112 | } 113 | 114 | @Override 115 | public int getTransactionIsolation() throws SQLException { 116 | return c.getTransactionIsolation(); 117 | } 118 | 119 | @Override 120 | public SQLWarning getWarnings() throws SQLException { 121 | return c.getWarnings(); 122 | } 123 | 124 | @Override 125 | public void clearWarnings() throws SQLException { 126 | c.clearWarnings(); 127 | } 128 | 129 | @Override 130 | public Statement createStatement(int resultSetType, int resultSetConcurrency) throws SQLException { 131 | return c.createStatement(resultSetType, resultSetConcurrency); 132 | } 133 | 134 | @Override 135 | public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency) throws SQLException { 136 | return new SingletonPreparedStatement(this, c.prepareStatement(sql, resultSetType, resultSetConcurrency)); 137 | } 138 | 139 | @Override 140 | public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency) throws SQLException { 141 | return c.prepareCall(sql, resultSetType, resultSetConcurrency); 142 | } 143 | 144 | @Override 145 | public Map> getTypeMap() throws SQLException { 146 | return c.getTypeMap(); 147 | } 148 | 149 | @Override 150 | public void setTypeMap(Map> map) throws SQLException { 151 | c.setTypeMap(map); 152 | } 153 | 154 | @Override 155 | public void setHoldability(int holdability) throws SQLException { 156 | c.setHoldability(holdability); 157 | } 158 | 159 | @Override 160 | public int getHoldability() throws SQLException { 161 | return c.getHoldability(); 162 | } 163 | 164 | @Override 165 | public Savepoint setSavepoint() throws SQLException { 166 | return c.setSavepoint(); 167 | } 168 | 169 | @Override 170 | public Savepoint setSavepoint(String name) throws SQLException { 171 | return c.setSavepoint(name); 172 | } 173 | 174 | @Override 175 | public void rollback(Savepoint savepoint) throws SQLException { 176 | c.rollback(savepoint); 177 | } 178 | 179 | @Override 180 | public void releaseSavepoint(Savepoint savepoint) throws SQLException { 181 | c.releaseSavepoint(savepoint); 182 | } 183 | 184 | @Override 185 | public Statement createStatement(int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException { 186 | return c.createStatement(resultSetType, resultSetConcurrency, resultSetHoldability); 187 | } 188 | 189 | @Override 190 | public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException { 191 | return new SingletonPreparedStatement(this, c.prepareStatement(sql, resultSetType, resultSetConcurrency, resultSetHoldability)); 192 | } 193 | 194 | @Override 195 | public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException { 196 | return c.prepareCall(sql, resultSetType, resultSetConcurrency, resultSetHoldability); 197 | } 198 | 199 | @Override 200 | public PreparedStatement prepareStatement(String sql, int autoGeneratedKeys) throws SQLException { 201 | return new SingletonPreparedStatement(this, c.prepareStatement(sql, autoGeneratedKeys)); 202 | } 203 | 204 | @Override 205 | public PreparedStatement prepareStatement(String sql, int[] columnIndexes) throws SQLException { 206 | return new SingletonPreparedStatement(this, c.prepareStatement(sql, columnIndexes)); 207 | } 208 | 209 | @Override 210 | public PreparedStatement prepareStatement(String sql, String[] columnNames) throws SQLException { 211 | return new SingletonPreparedStatement(this, c.prepareStatement(sql, columnNames)); 212 | } 213 | 214 | @Override 215 | public Clob createClob() throws SQLException { 216 | return c.createClob(); 217 | } 218 | 219 | @Override 220 | public Blob createBlob() throws SQLException { 221 | return c.createBlob(); 222 | } 223 | 224 | @Override 225 | public NClob createNClob() throws SQLException { 226 | return c.createNClob(); 227 | } 228 | 229 | @Override 230 | public SQLXML createSQLXML() throws SQLException { 231 | return c.createSQLXML(); 232 | } 233 | 234 | @Override 235 | public boolean isValid(int timeout) throws SQLException { 236 | return c.isValid(timeout); 237 | } 238 | 239 | @Override 240 | public void setClientInfo(String name, String value) throws SQLClientInfoException { 241 | c.setClientInfo(name, value); 242 | } 243 | 244 | @Override 245 | public void setClientInfo(Properties properties) throws SQLClientInfoException { 246 | c.setClientInfo(properties); 247 | } 248 | 249 | @Override 250 | public String getClientInfo(String name) throws SQLException { 251 | return c.getClientInfo(name); 252 | } 253 | 254 | @Override 255 | public Properties getClientInfo() throws SQLException { 256 | return c.getClientInfo(); 257 | } 258 | 259 | @Override 260 | public Array createArrayOf(String typeName, Object[] elements) throws SQLException { 261 | return c.createArrayOf(typeName, elements); 262 | } 263 | 264 | @Override 265 | public Struct createStruct(String typeName, Object[] attributes) throws SQLException { 266 | return c.createStruct(typeName, attributes); 267 | } 268 | 269 | @Override 270 | public void setSchema(String schema) throws SQLException { 271 | c.setSchema(schema); 272 | } 273 | 274 | @Override 275 | public String getSchema() throws SQLException { 276 | return c.getSchema(); 277 | } 278 | 279 | @Override 280 | public void abort(Executor executor) throws SQLException { 281 | c.abort(executor); 282 | } 283 | 284 | @Override 285 | public void setNetworkTimeout(Executor executor, int milliseconds) throws SQLException { 286 | c.setNetworkTimeout(executor, milliseconds); 287 | } 288 | 289 | @Override 290 | public int getNetworkTimeout() throws SQLException { 291 | return c.getNetworkTimeout(); 292 | } 293 | 294 | @Override 295 | public T unwrap(Class iface) throws SQLException { 296 | return c.unwrap(iface); 297 | } 298 | 299 | @Override 300 | public boolean isWrapperFor(Class iface) throws SQLException { 301 | return c.isWrapperFor(iface); 302 | } 303 | } 304 | -------------------------------------------------------------------------------- /jactiverecord/src/main/java/me/zzp/ar/pool/SingletonDataSource.java: -------------------------------------------------------------------------------- 1 | package me.zzp.ar.pool; 2 | 3 | import java.io.PrintWriter; 4 | import java.sql.Connection; 5 | import java.sql.DriverManager; 6 | import java.sql.SQLException; 7 | import java.sql.SQLFeatureNotSupportedException; 8 | import java.util.Properties; 9 | import java.util.logging.Logger; 10 | import javax.sql.DataSource; 11 | 12 | /** 13 | *

jActiveRecord默认DataSource实现,永远只返回同一个数据库连接。 14 | * 因此,内存型的数据库也能正常工作。

15 | * 16 | * @since 2.0 17 | * @author redraiment 18 | */ 19 | public final class SingletonDataSource implements DataSource { 20 | private final Connection instance; 21 | 22 | /** 23 | * 提供连接数据库基本信息。 24 | * 25 | * @param url 数据库连接地址 26 | * @param info 包含用户名、密码等登入信息。 27 | * @throws java.sql.SQLException 连接数据库失败 28 | */ 29 | public SingletonDataSource(String url, Properties info) throws SQLException { 30 | final Connection c = DriverManager.getConnection(url, info); 31 | Runtime.getRuntime().addShutdownHook(new Thread() { 32 | @Override 33 | public void run() { 34 | try { 35 | c.close(); 36 | } catch (SQLException e) { 37 | throw new RuntimeException("close database fail"); 38 | } 39 | } 40 | }); 41 | instance = new SingletonConnection(c); 42 | } 43 | 44 | /** 45 | * 每次调用时均返回一个新的数据库连接。 46 | * 47 | * @return 一个新的数据库连接。 48 | * @throws SQLException 连接数据库失败。 49 | */ 50 | @Override 51 | public Connection getConnection() throws SQLException { 52 | return instance; 53 | } 54 | 55 | /** 56 | * 每次调用时均返回一个新的数据库连接。 57 | * 58 | * @return 一个新的数据库连接。 59 | * @throws SQLException 连接数据库失败。 60 | */ 61 | @Override 62 | public Connection getConnection(String username, String password) throws SQLException { 63 | return instance; 64 | } 65 | 66 | /** 67 | * 不支持,永远不会被调用。 68 | * 69 | * @return 无 70 | * @throws SQLException 从不 71 | */ 72 | @Override 73 | public PrintWriter getLogWriter() throws SQLException { 74 | throw new UnsupportedOperationException("Not supported yet."); 75 | } 76 | 77 | /** 78 | * 不支持,永远不会被调用。 79 | * 80 | * @throws SQLException 从不 81 | */ 82 | @Override 83 | public void setLogWriter(PrintWriter out) throws SQLException { 84 | throw new UnsupportedOperationException("Not supported yet."); 85 | } 86 | 87 | /** 88 | * 不支持,永远不会被调用。 89 | * 90 | * @throws SQLException 从不 91 | */ 92 | @Override 93 | public void setLoginTimeout(int seconds) throws SQLException { 94 | throw new UnsupportedOperationException("Not supported yet."); 95 | } 96 | 97 | /** 98 | * 不支持,永远不会被调用。 99 | * 100 | * @return 无 101 | * @throws SQLException 从不 102 | */ 103 | @Override 104 | public int getLoginTimeout() throws SQLException { 105 | throw new UnsupportedOperationException("Not supported yet."); 106 | } 107 | 108 | /** 109 | * 不支持,永远不会被调用。 110 | * 111 | * @return 无 112 | * @throws SQLFeatureNotSupportedException 从不 113 | */ 114 | @Override 115 | public Logger getParentLogger() throws SQLFeatureNotSupportedException { 116 | throw new UnsupportedOperationException("Not supported yet."); 117 | } 118 | 119 | /** 120 | * 不支持,永远不会被调用。 121 | * 122 | * @param 类型 123 | * @return 无 124 | * @throws SQLException 从不 125 | */ 126 | @Override 127 | public T unwrap(Class iface) throws SQLException { 128 | throw new UnsupportedOperationException("Not supported yet."); 129 | } 130 | 131 | /** 132 | * 不支持,永远不会被调用。 133 | * 134 | * @return 无 135 | * @throws SQLException 从不 136 | */ 137 | @Override 138 | public boolean isWrapperFor(Class iface) throws SQLException { 139 | throw new UnsupportedOperationException("Not supported yet."); 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /jactiverecord/src/main/java/me/zzp/ar/pool/SingletonPreparedStatement.java: -------------------------------------------------------------------------------- 1 | package me.zzp.ar.pool; 2 | 3 | import java.io.InputStream; 4 | import java.io.Reader; 5 | import java.math.BigDecimal; 6 | import java.net.URL; 7 | import java.sql.Array; 8 | import java.sql.Blob; 9 | import java.sql.Clob; 10 | import java.sql.Connection; 11 | import java.sql.Date; 12 | import java.sql.NClob; 13 | import java.sql.ParameterMetaData; 14 | import java.sql.PreparedStatement; 15 | import java.sql.Ref; 16 | import java.sql.ResultSet; 17 | import java.sql.ResultSetMetaData; 18 | import java.sql.RowId; 19 | import java.sql.SQLException; 20 | import java.sql.SQLWarning; 21 | import java.sql.SQLXML; 22 | import java.sql.Time; 23 | import java.sql.Timestamp; 24 | import java.util.Calendar; 25 | 26 | /** 27 | * 28 | * @author redraiment 29 | * @since 2.2 30 | */ 31 | final class SingletonPreparedStatement implements PreparedStatement { 32 | private final Connection c; 33 | private final PreparedStatement s; 34 | 35 | SingletonPreparedStatement(Connection c, PreparedStatement s) { 36 | this.c = c; 37 | this.s = s; 38 | } 39 | 40 | @Override 41 | public ResultSet executeQuery() throws SQLException { 42 | return new SingletonResultSet(this, s.executeQuery()); 43 | } 44 | 45 | @Override 46 | public int executeUpdate() throws SQLException { 47 | return s.executeUpdate(); 48 | } 49 | 50 | @Override 51 | public void setNull(int parameterIndex, int sqlType) throws SQLException { 52 | s.setNull(parameterIndex, sqlType); 53 | } 54 | 55 | @Override 56 | public void setBoolean(int parameterIndex, boolean x) throws SQLException { 57 | s.setBoolean(parameterIndex, x); 58 | } 59 | 60 | @Override 61 | public void setByte(int parameterIndex, byte x) throws SQLException { 62 | s.setByte(parameterIndex, x); 63 | } 64 | 65 | @Override 66 | public void setShort(int parameterIndex, short x) throws SQLException { 67 | s.setShort(parameterIndex, x); 68 | } 69 | 70 | @Override 71 | public void setInt(int parameterIndex, int x) throws SQLException { 72 | s.setInt(parameterIndex, x); 73 | } 74 | 75 | @Override 76 | public void setLong(int parameterIndex, long x) throws SQLException { 77 | s.setLong(parameterIndex, x); 78 | } 79 | 80 | @Override 81 | public void setFloat(int parameterIndex, float x) throws SQLException { 82 | s.setFloat(parameterIndex, x); 83 | } 84 | 85 | @Override 86 | public void setDouble(int parameterIndex, double x) throws SQLException { 87 | s.setDouble(parameterIndex, x); 88 | } 89 | 90 | @Override 91 | public void setBigDecimal(int parameterIndex, BigDecimal x) throws SQLException { 92 | s.setBigDecimal(parameterIndex, x); 93 | } 94 | 95 | @Override 96 | public void setString(int parameterIndex, String x) throws SQLException { 97 | s.setString(parameterIndex, x); 98 | } 99 | 100 | @Override 101 | public void setBytes(int parameterIndex, byte[] x) throws SQLException { 102 | s.setBytes(parameterIndex, x); 103 | } 104 | 105 | @Override 106 | public void setDate(int parameterIndex, Date x) throws SQLException { 107 | s.setDate(parameterIndex, x); 108 | } 109 | 110 | @Override 111 | public void setTime(int parameterIndex, Time x) throws SQLException { 112 | s.setTime(parameterIndex, x); 113 | } 114 | 115 | @Override 116 | public void setTimestamp(int parameterIndex, Timestamp x) throws SQLException { 117 | s.setTimestamp(parameterIndex, x); 118 | } 119 | 120 | @Override 121 | public void setAsciiStream(int parameterIndex, InputStream x, int length) throws SQLException { 122 | s.setAsciiStream(parameterIndex, x, length); 123 | } 124 | 125 | @Override 126 | public void setUnicodeStream(int parameterIndex, InputStream x, int length) throws SQLException { 127 | s.setUnicodeStream(parameterIndex, x, length); 128 | } 129 | 130 | @Override 131 | public void setBinaryStream(int parameterIndex, InputStream x, int length) throws SQLException { 132 | s.setBinaryStream(parameterIndex, x, length); 133 | } 134 | 135 | @Override 136 | public void clearParameters() throws SQLException { 137 | s.clearParameters(); 138 | } 139 | 140 | @Override 141 | public void setObject(int parameterIndex, Object x, int targetSqlType) throws SQLException { 142 | s.setObject(parameterIndex, x, targetSqlType); 143 | } 144 | 145 | @Override 146 | public void setObject(int parameterIndex, Object x) throws SQLException { 147 | s.setObject(parameterIndex, x); 148 | } 149 | 150 | @Override 151 | public boolean execute() throws SQLException { 152 | return s.execute(); 153 | } 154 | 155 | @Override 156 | public void addBatch() throws SQLException { 157 | s.addBatch(); 158 | } 159 | 160 | @Override 161 | public void setCharacterStream(int parameterIndex, Reader reader, int length) throws SQLException { 162 | s.setCharacterStream(parameterIndex, reader, length); 163 | } 164 | 165 | @Override 166 | public void setRef(int parameterIndex, Ref x) throws SQLException { 167 | s.setRef(parameterIndex, x); 168 | } 169 | 170 | @Override 171 | public void setBlob(int parameterIndex, Blob x) throws SQLException { 172 | s.setBlob(parameterIndex, x); 173 | } 174 | 175 | @Override 176 | public void setClob(int parameterIndex, Clob x) throws SQLException { 177 | s.setClob(parameterIndex, x); 178 | } 179 | 180 | @Override 181 | public void setArray(int parameterIndex, Array x) throws SQLException { 182 | s.setArray(parameterIndex, x); 183 | } 184 | 185 | @Override 186 | public ResultSetMetaData getMetaData() throws SQLException { 187 | return s.getMetaData(); 188 | } 189 | 190 | @Override 191 | public void setDate(int parameterIndex, Date x, Calendar cal) throws SQLException { 192 | s.setDate(parameterIndex, x, cal); 193 | } 194 | 195 | @Override 196 | public void setTime(int parameterIndex, Time x, Calendar cal) throws SQLException { 197 | s.setTime(parameterIndex, x, cal); 198 | } 199 | 200 | @Override 201 | public void setTimestamp(int parameterIndex, Timestamp x, Calendar cal) throws SQLException { 202 | s.setTimestamp(parameterIndex, x, cal); 203 | } 204 | 205 | @Override 206 | public void setNull(int parameterIndex, int sqlType, String typeName) throws SQLException { 207 | s.setNull(parameterIndex, sqlType, typeName); 208 | } 209 | 210 | @Override 211 | public void setURL(int parameterIndex, URL x) throws SQLException { 212 | s.setURL(parameterIndex, x); 213 | } 214 | 215 | @Override 216 | public ParameterMetaData getParameterMetaData() throws SQLException { 217 | return s.getParameterMetaData(); 218 | } 219 | 220 | @Override 221 | public void setRowId(int parameterIndex, RowId x) throws SQLException { 222 | s.setRowId(parameterIndex, x); 223 | } 224 | 225 | @Override 226 | public void setNString(int parameterIndex, String value) throws SQLException { 227 | s.setNString(parameterIndex, value); 228 | } 229 | 230 | @Override 231 | public void setNCharacterStream(int parameterIndex, Reader value, long length) throws SQLException { 232 | s.setNCharacterStream(parameterIndex, value, length); 233 | } 234 | 235 | @Override 236 | public void setNClob(int parameterIndex, NClob value) throws SQLException { 237 | s.setNClob(parameterIndex, value); 238 | } 239 | 240 | @Override 241 | public void setClob(int parameterIndex, Reader reader, long length) throws SQLException { 242 | s.setClob(parameterIndex, reader, length); 243 | } 244 | 245 | @Override 246 | public void setBlob(int parameterIndex, InputStream inputStream, long length) throws SQLException { 247 | s.setBlob(parameterIndex, inputStream, length); 248 | } 249 | 250 | @Override 251 | public void setNClob(int parameterIndex, Reader reader, long length) throws SQLException { 252 | s.setNClob(parameterIndex, reader, length); 253 | } 254 | 255 | @Override 256 | public void setSQLXML(int parameterIndex, SQLXML xmlObject) throws SQLException { 257 | s.setSQLXML(parameterIndex, xmlObject); 258 | } 259 | 260 | @Override 261 | public void setObject(int parameterIndex, Object x, int targetSqlType, int scaleOrLength) throws SQLException { 262 | s.setObject(parameterIndex, x, targetSqlType, scaleOrLength); 263 | } 264 | 265 | @Override 266 | public void setAsciiStream(int parameterIndex, InputStream x, long length) throws SQLException { 267 | s.setAsciiStream(parameterIndex, x, length); 268 | } 269 | 270 | @Override 271 | public void setBinaryStream(int parameterIndex, InputStream x, long length) throws SQLException { 272 | s.setBinaryStream(parameterIndex, x, length); 273 | } 274 | 275 | @Override 276 | public void setCharacterStream(int parameterIndex, Reader reader, long length) throws SQLException { 277 | s.setCharacterStream(parameterIndex, reader, length); 278 | } 279 | 280 | @Override 281 | public void setAsciiStream(int parameterIndex, InputStream x) throws SQLException { 282 | s.setAsciiStream(parameterIndex, x); 283 | } 284 | 285 | @Override 286 | public void setBinaryStream(int parameterIndex, InputStream x) throws SQLException { 287 | s.setBinaryStream(parameterIndex, x); 288 | } 289 | 290 | @Override 291 | public void setCharacterStream(int parameterIndex, Reader reader) throws SQLException { 292 | s.setCharacterStream(parameterIndex, reader); 293 | } 294 | 295 | @Override 296 | public void setNCharacterStream(int parameterIndex, Reader value) throws SQLException { 297 | s.setNCharacterStream(parameterIndex, value); 298 | } 299 | 300 | @Override 301 | public void setClob(int parameterIndex, Reader reader) throws SQLException { 302 | s.setClob(parameterIndex, reader); 303 | } 304 | 305 | @Override 306 | public void setBlob(int parameterIndex, InputStream inputStream) throws SQLException { 307 | s.setBlob(parameterIndex, inputStream); 308 | } 309 | 310 | @Override 311 | public void setNClob(int parameterIndex, Reader reader) throws SQLException { 312 | s.setNClob(parameterIndex, reader); 313 | } 314 | 315 | @Override 316 | public ResultSet executeQuery(String sql) throws SQLException { 317 | return new SingletonResultSet(this, s.executeQuery(sql)); 318 | } 319 | 320 | @Override 321 | public int executeUpdate(String sql) throws SQLException { 322 | return s.executeUpdate(sql); 323 | } 324 | 325 | @Override 326 | public void close() throws SQLException { 327 | s.close(); 328 | } 329 | 330 | @Override 331 | public int getMaxFieldSize() throws SQLException { 332 | return s.getMaxFieldSize(); 333 | } 334 | 335 | @Override 336 | public void setMaxFieldSize(int max) throws SQLException { 337 | s.setMaxFieldSize(max); 338 | } 339 | 340 | @Override 341 | public int getMaxRows() throws SQLException { 342 | return s.getMaxRows(); 343 | } 344 | 345 | @Override 346 | public void setMaxRows(int max) throws SQLException { 347 | s.setMaxRows(max); 348 | } 349 | 350 | @Override 351 | public void setEscapeProcessing(boolean enable) throws SQLException { 352 | s.setEscapeProcessing(enable); 353 | } 354 | 355 | @Override 356 | public int getQueryTimeout() throws SQLException { 357 | return s.getQueryTimeout(); 358 | } 359 | 360 | @Override 361 | public void setQueryTimeout(int seconds) throws SQLException { 362 | s.setQueryTimeout(seconds); 363 | } 364 | 365 | @Override 366 | public void cancel() throws SQLException { 367 | s.cancel(); 368 | } 369 | 370 | @Override 371 | public SQLWarning getWarnings() throws SQLException { 372 | return s.getWarnings(); 373 | } 374 | 375 | @Override 376 | public void clearWarnings() throws SQLException { 377 | s.clearWarnings(); 378 | } 379 | 380 | @Override 381 | public void setCursorName(String name) throws SQLException { 382 | s.setCursorName(name); 383 | } 384 | 385 | @Override 386 | public boolean execute(String sql) throws SQLException { 387 | return s.execute(sql); 388 | } 389 | 390 | @Override 391 | public ResultSet getResultSet() throws SQLException { 392 | return new SingletonResultSet(this, s.getResultSet()); 393 | } 394 | 395 | @Override 396 | public int getUpdateCount() throws SQLException { 397 | return s.getUpdateCount(); 398 | } 399 | 400 | @Override 401 | public boolean getMoreResults() throws SQLException { 402 | return s.getMoreResults(); 403 | } 404 | 405 | @Override 406 | public void setFetchDirection(int direction) throws SQLException { 407 | s.setFetchDirection(direction); 408 | } 409 | 410 | @Override 411 | public int getFetchDirection() throws SQLException { 412 | return s.getFetchDirection(); 413 | } 414 | 415 | @Override 416 | public void setFetchSize(int rows) throws SQLException { 417 | s.setFetchSize(rows); 418 | } 419 | 420 | @Override 421 | public int getFetchSize() throws SQLException { 422 | return s.getFetchSize(); 423 | } 424 | 425 | @Override 426 | public int getResultSetConcurrency() throws SQLException { 427 | return s.getResultSetConcurrency(); 428 | } 429 | 430 | @Override 431 | public int getResultSetType() throws SQLException { 432 | return s.getResultSetType(); 433 | } 434 | 435 | @Override 436 | public void addBatch(String sql) throws SQLException { 437 | s.addBatch(sql); 438 | } 439 | 440 | @Override 441 | public void clearBatch() throws SQLException { 442 | s.clearBatch(); 443 | } 444 | 445 | @Override 446 | public int[] executeBatch() throws SQLException { 447 | return s.executeBatch(); 448 | } 449 | 450 | @Override 451 | public Connection getConnection() throws SQLException { 452 | return c; 453 | } 454 | 455 | @Override 456 | public boolean getMoreResults(int current) throws SQLException { 457 | return s.getMoreResults(current); 458 | } 459 | 460 | @Override 461 | public ResultSet getGeneratedKeys() throws SQLException { 462 | return new SingletonResultSet(this, s.getGeneratedKeys()); 463 | } 464 | 465 | @Override 466 | public int executeUpdate(String sql, int autoGeneratedKeys) throws SQLException { 467 | return s.executeUpdate(sql, autoGeneratedKeys); 468 | } 469 | 470 | @Override 471 | public int executeUpdate(String sql, int[] columnIndexes) throws SQLException { 472 | return s.executeUpdate(sql, columnIndexes); 473 | } 474 | 475 | @Override 476 | public int executeUpdate(String sql, String[] columnNames) throws SQLException { 477 | return s.executeUpdate(sql, columnNames); 478 | } 479 | 480 | @Override 481 | public boolean execute(String sql, int autoGeneratedKeys) throws SQLException { 482 | return s.execute(sql, autoGeneratedKeys); 483 | } 484 | 485 | @Override 486 | public boolean execute(String sql, int[] columnIndexes) throws SQLException { 487 | return s.execute(sql, columnIndexes); 488 | } 489 | 490 | @Override 491 | public boolean execute(String sql, String[] columnNames) throws SQLException { 492 | return s.execute(sql, columnNames); 493 | } 494 | 495 | @Override 496 | public int getResultSetHoldability() throws SQLException { 497 | return s.getResultSetHoldability(); 498 | } 499 | 500 | @Override 501 | public boolean isClosed() throws SQLException { 502 | return s.isClosed(); 503 | } 504 | 505 | @Override 506 | public void setPoolable(boolean poolable) throws SQLException { 507 | s.setPoolable(poolable); 508 | } 509 | 510 | @Override 511 | public boolean isPoolable() throws SQLException { 512 | return s.isPoolable(); 513 | } 514 | 515 | @Override 516 | public void closeOnCompletion() throws SQLException { 517 | s.closeOnCompletion(); 518 | } 519 | 520 | @Override 521 | public boolean isCloseOnCompletion() throws SQLException { 522 | return s.isCloseOnCompletion(); 523 | } 524 | 525 | @Override 526 | public T unwrap(Class iface) throws SQLException { 527 | return s.unwrap(iface); 528 | } 529 | 530 | @Override 531 | public boolean isWrapperFor(Class iface) throws SQLException { 532 | return s.isWrapperFor(iface); 533 | } 534 | } 535 | -------------------------------------------------------------------------------- /jactiverecord/src/main/java/me/zzp/ar/pool/SingletonResultSet.java: -------------------------------------------------------------------------------- 1 | package me.zzp.ar.pool; 2 | 3 | import java.io.InputStream; 4 | import java.io.Reader; 5 | import java.math.BigDecimal; 6 | import java.net.URL; 7 | import java.sql.Array; 8 | import java.sql.Blob; 9 | import java.sql.Clob; 10 | import java.sql.Date; 11 | import java.sql.NClob; 12 | import java.sql.Ref; 13 | import java.sql.ResultSet; 14 | import java.sql.ResultSetMetaData; 15 | import java.sql.RowId; 16 | import java.sql.SQLException; 17 | import java.sql.SQLWarning; 18 | import java.sql.SQLXML; 19 | import java.sql.Statement; 20 | import java.sql.Time; 21 | import java.sql.Timestamp; 22 | import java.util.Calendar; 23 | import java.util.Map; 24 | 25 | /** 26 | * 27 | * @author redraiment 28 | * @since 2.2 29 | */ 30 | final class SingletonResultSet implements ResultSet { 31 | private final Statement s; 32 | private final ResultSet rs; 33 | 34 | SingletonResultSet(Statement s, ResultSet rs) { 35 | this.s = s; 36 | this.rs = rs; 37 | } 38 | 39 | @Override 40 | public boolean next() throws SQLException { 41 | return rs.next(); 42 | } 43 | 44 | @Override 45 | public void close() throws SQLException { 46 | rs.close(); 47 | } 48 | 49 | @Override 50 | public boolean wasNull() throws SQLException { 51 | return rs.wasNull(); 52 | } 53 | 54 | @Override 55 | public String getString(int columnIndex) throws SQLException { 56 | return rs.getString(columnIndex); 57 | } 58 | 59 | @Override 60 | public boolean getBoolean(int columnIndex) throws SQLException { 61 | return rs.getBoolean(columnIndex); 62 | } 63 | 64 | @Override 65 | public byte getByte(int columnIndex) throws SQLException { 66 | return rs.getByte(columnIndex); 67 | } 68 | 69 | @Override 70 | public short getShort(int columnIndex) throws SQLException { 71 | return rs.getShort(columnIndex); 72 | } 73 | 74 | @Override 75 | public int getInt(int columnIndex) throws SQLException { 76 | return rs.getInt(columnIndex); 77 | } 78 | 79 | @Override 80 | public long getLong(int columnIndex) throws SQLException { 81 | return rs.getLong(columnIndex); 82 | } 83 | 84 | @Override 85 | public float getFloat(int columnIndex) throws SQLException { 86 | return rs.getFloat(columnIndex); 87 | } 88 | 89 | @Override 90 | public double getDouble(int columnIndex) throws SQLException { 91 | return rs.getDouble(columnIndex); 92 | } 93 | 94 | @Override 95 | public BigDecimal getBigDecimal(int columnIndex, int scale) throws SQLException { 96 | return rs.getBigDecimal(columnIndex, scale); 97 | } 98 | 99 | @Override 100 | public byte[] getBytes(int columnIndex) throws SQLException { 101 | return rs.getBytes(columnIndex); 102 | } 103 | 104 | @Override 105 | public Date getDate(int columnIndex) throws SQLException { 106 | return rs.getDate(columnIndex); 107 | } 108 | 109 | @Override 110 | public Time getTime(int columnIndex) throws SQLException { 111 | return rs.getTime(columnIndex); 112 | } 113 | 114 | @Override 115 | public Timestamp getTimestamp(int columnIndex) throws SQLException { 116 | return rs.getTimestamp(columnIndex); 117 | } 118 | 119 | @Override 120 | public InputStream getAsciiStream(int columnIndex) throws SQLException { 121 | return rs.getAsciiStream(columnIndex); 122 | } 123 | 124 | @Override 125 | public InputStream getUnicodeStream(int columnIndex) throws SQLException { 126 | return rs.getUnicodeStream(columnIndex); 127 | } 128 | 129 | @Override 130 | public InputStream getBinaryStream(int columnIndex) throws SQLException { 131 | return rs.getBinaryStream(columnIndex); 132 | } 133 | 134 | @Override 135 | public String getString(String columnLabel) throws SQLException { 136 | return rs.getString(columnLabel); 137 | } 138 | 139 | @Override 140 | public boolean getBoolean(String columnLabel) throws SQLException { 141 | return rs.getBoolean(columnLabel); 142 | } 143 | 144 | @Override 145 | public byte getByte(String columnLabel) throws SQLException { 146 | return rs.getByte(columnLabel); 147 | } 148 | 149 | @Override 150 | public short getShort(String columnLabel) throws SQLException { 151 | return rs.getShort(columnLabel); 152 | } 153 | 154 | @Override 155 | public int getInt(String columnLabel) throws SQLException { 156 | return rs.getInt(columnLabel); 157 | } 158 | 159 | @Override 160 | public long getLong(String columnLabel) throws SQLException { 161 | return rs.getLong(columnLabel); 162 | } 163 | 164 | @Override 165 | public float getFloat(String columnLabel) throws SQLException { 166 | return rs.getFloat(columnLabel); 167 | } 168 | 169 | @Override 170 | public double getDouble(String columnLabel) throws SQLException { 171 | return rs.getDouble(columnLabel); 172 | } 173 | 174 | @Override 175 | public BigDecimal getBigDecimal(String columnLabel, int scale) throws SQLException { 176 | return rs.getBigDecimal(columnLabel, scale); 177 | } 178 | 179 | @Override 180 | public byte[] getBytes(String columnLabel) throws SQLException { 181 | return rs.getBytes(columnLabel); 182 | } 183 | 184 | @Override 185 | public Date getDate(String columnLabel) throws SQLException { 186 | return rs.getDate(columnLabel); 187 | } 188 | 189 | @Override 190 | public Time getTime(String columnLabel) throws SQLException { 191 | return rs.getTime(columnLabel); 192 | } 193 | 194 | @Override 195 | public Timestamp getTimestamp(String columnLabel) throws SQLException { 196 | return rs.getTimestamp(columnLabel); 197 | } 198 | 199 | @Override 200 | public InputStream getAsciiStream(String columnLabel) throws SQLException { 201 | return rs.getAsciiStream(columnLabel); 202 | } 203 | 204 | @Override 205 | public InputStream getUnicodeStream(String columnLabel) throws SQLException { 206 | return rs.getUnicodeStream(columnLabel); 207 | } 208 | 209 | @Override 210 | public InputStream getBinaryStream(String columnLabel) throws SQLException { 211 | return rs.getBinaryStream(columnLabel); 212 | } 213 | 214 | @Override 215 | public SQLWarning getWarnings() throws SQLException { 216 | return rs.getWarnings(); 217 | } 218 | 219 | @Override 220 | public void clearWarnings() throws SQLException { 221 | rs.clearWarnings(); 222 | } 223 | 224 | @Override 225 | public String getCursorName() throws SQLException { 226 | return rs.getCursorName(); 227 | } 228 | 229 | @Override 230 | public ResultSetMetaData getMetaData() throws SQLException { 231 | return rs.getMetaData(); 232 | } 233 | 234 | @Override 235 | public Object getObject(int columnIndex) throws SQLException { 236 | return rs.getObject(columnIndex); 237 | } 238 | 239 | @Override 240 | public Object getObject(String columnLabel) throws SQLException { 241 | return rs.getObject(columnLabel); 242 | } 243 | 244 | @Override 245 | public int findColumn(String columnLabel) throws SQLException { 246 | return rs.findColumn(columnLabel); 247 | } 248 | 249 | @Override 250 | public Reader getCharacterStream(int columnIndex) throws SQLException { 251 | return rs.getCharacterStream(columnIndex); 252 | } 253 | 254 | @Override 255 | public Reader getCharacterStream(String columnLabel) throws SQLException { 256 | return rs.getCharacterStream(columnLabel); 257 | } 258 | 259 | @Override 260 | public BigDecimal getBigDecimal(int columnIndex) throws SQLException { 261 | return rs.getBigDecimal(columnIndex); 262 | } 263 | 264 | @Override 265 | public BigDecimal getBigDecimal(String columnLabel) throws SQLException { 266 | return rs.getBigDecimal(columnLabel); 267 | } 268 | 269 | @Override 270 | public boolean isBeforeFirst() throws SQLException { 271 | return rs.isBeforeFirst(); 272 | } 273 | 274 | @Override 275 | public boolean isAfterLast() throws SQLException { 276 | return rs.isAfterLast(); 277 | } 278 | 279 | @Override 280 | public boolean isFirst() throws SQLException { 281 | return rs.isFirst(); 282 | } 283 | 284 | @Override 285 | public boolean isLast() throws SQLException { 286 | return rs.isLast(); 287 | } 288 | 289 | @Override 290 | public void beforeFirst() throws SQLException { 291 | rs.beforeFirst(); 292 | } 293 | 294 | @Override 295 | public void afterLast() throws SQLException { 296 | rs.afterLast(); 297 | } 298 | 299 | @Override 300 | public boolean first() throws SQLException { 301 | return rs.first(); 302 | } 303 | 304 | @Override 305 | public boolean last() throws SQLException { 306 | return rs.last(); 307 | } 308 | 309 | @Override 310 | public int getRow() throws SQLException { 311 | return rs.getRow(); 312 | } 313 | 314 | @Override 315 | public boolean absolute(int row) throws SQLException { 316 | return rs.absolute(row); 317 | } 318 | 319 | @Override 320 | public boolean relative(int rows) throws SQLException { 321 | return rs.relative(rows); 322 | } 323 | 324 | @Override 325 | public boolean previous() throws SQLException { 326 | return rs.previous(); 327 | } 328 | 329 | @Override 330 | public void setFetchDirection(int direction) throws SQLException { 331 | rs.setFetchDirection(direction); 332 | } 333 | 334 | @Override 335 | public int getFetchDirection() throws SQLException { 336 | return rs.getFetchDirection(); 337 | } 338 | 339 | @Override 340 | public void setFetchSize(int rows) throws SQLException { 341 | rs.setFetchSize(rows); 342 | } 343 | 344 | @Override 345 | public int getFetchSize() throws SQLException { 346 | return rs.getFetchSize(); 347 | } 348 | 349 | @Override 350 | public int getType() throws SQLException { 351 | return rs.getType(); 352 | } 353 | 354 | @Override 355 | public int getConcurrency() throws SQLException { 356 | return rs.getConcurrency(); 357 | } 358 | 359 | @Override 360 | public boolean rowUpdated() throws SQLException { 361 | return rs.rowUpdated(); 362 | } 363 | 364 | @Override 365 | public boolean rowInserted() throws SQLException { 366 | return rs.rowInserted(); 367 | } 368 | 369 | @Override 370 | public boolean rowDeleted() throws SQLException { 371 | return rs.rowDeleted(); 372 | } 373 | 374 | @Override 375 | public void updateNull(int columnIndex) throws SQLException { 376 | rs.updateNull(columnIndex); 377 | } 378 | 379 | @Override 380 | public void updateBoolean(int columnIndex, boolean x) throws SQLException { 381 | rs.updateBoolean(columnIndex, x); 382 | } 383 | 384 | @Override 385 | public void updateByte(int columnIndex, byte x) throws SQLException { 386 | rs.updateByte(columnIndex, x); 387 | } 388 | 389 | @Override 390 | public void updateShort(int columnIndex, short x) throws SQLException { 391 | rs.updateShort(columnIndex, x); 392 | } 393 | 394 | @Override 395 | public void updateInt(int columnIndex, int x) throws SQLException { 396 | rs.updateInt(columnIndex, x); 397 | } 398 | 399 | @Override 400 | public void updateLong(int columnIndex, long x) throws SQLException { 401 | rs.updateLong(columnIndex, x); 402 | } 403 | 404 | @Override 405 | public void updateFloat(int columnIndex, float x) throws SQLException { 406 | rs.updateFloat(columnIndex, x); 407 | } 408 | 409 | @Override 410 | public void updateDouble(int columnIndex, double x) throws SQLException { 411 | rs.updateDouble(columnIndex, x); 412 | } 413 | 414 | @Override 415 | public void updateBigDecimal(int columnIndex, BigDecimal x) throws SQLException { 416 | rs.updateBigDecimal(columnIndex, x); 417 | } 418 | 419 | @Override 420 | public void updateString(int columnIndex, String x) throws SQLException { 421 | rs.updateString(columnIndex, x); 422 | } 423 | 424 | @Override 425 | public void updateBytes(int columnIndex, byte[] x) throws SQLException { 426 | rs.updateBytes(columnIndex, x); 427 | } 428 | 429 | @Override 430 | public void updateDate(int columnIndex, Date x) throws SQLException { 431 | rs.updateDate(columnIndex, x); 432 | } 433 | 434 | @Override 435 | public void updateTime(int columnIndex, Time x) throws SQLException { 436 | rs.updateTime(columnIndex, x); 437 | } 438 | 439 | @Override 440 | public void updateTimestamp(int columnIndex, Timestamp x) throws SQLException { 441 | rs.updateTimestamp(columnIndex, x); 442 | } 443 | 444 | @Override 445 | public void updateAsciiStream(int columnIndex, InputStream x, int length) throws SQLException { 446 | rs.updateAsciiStream(columnIndex, x, length); 447 | } 448 | 449 | @Override 450 | public void updateBinaryStream(int columnIndex, InputStream x, int length) throws SQLException { 451 | rs.updateBinaryStream(columnIndex, x, length); 452 | } 453 | 454 | @Override 455 | public void updateCharacterStream(int columnIndex, Reader x, int length) throws SQLException { 456 | rs.updateCharacterStream(columnIndex, x, length); 457 | } 458 | 459 | @Override 460 | public void updateObject(int columnIndex, Object x, int scaleOrLength) throws SQLException { 461 | rs.updateObject(columnIndex, x, scaleOrLength); 462 | } 463 | 464 | @Override 465 | public void updateObject(int columnIndex, Object x) throws SQLException { 466 | rs.updateObject(columnIndex, x); 467 | } 468 | 469 | @Override 470 | public void updateNull(String columnLabel) throws SQLException { 471 | rs.updateNull(columnLabel); 472 | } 473 | 474 | @Override 475 | public void updateBoolean(String columnLabel, boolean x) throws SQLException { 476 | rs.updateBoolean(columnLabel, x); 477 | } 478 | 479 | @Override 480 | public void updateByte(String columnLabel, byte x) throws SQLException { 481 | rs.updateByte(columnLabel, x); 482 | } 483 | 484 | @Override 485 | public void updateShort(String columnLabel, short x) throws SQLException { 486 | rs.updateShort(columnLabel, x); 487 | } 488 | 489 | @Override 490 | public void updateInt(String columnLabel, int x) throws SQLException { 491 | rs.updateInt(columnLabel, x); 492 | } 493 | 494 | @Override 495 | public void updateLong(String columnLabel, long x) throws SQLException { 496 | rs.updateLong(columnLabel, x); 497 | } 498 | 499 | @Override 500 | public void updateFloat(String columnLabel, float x) throws SQLException { 501 | rs.updateFloat(columnLabel, x); 502 | } 503 | 504 | @Override 505 | public void updateDouble(String columnLabel, double x) throws SQLException { 506 | rs.updateDouble(columnLabel, x); 507 | } 508 | 509 | @Override 510 | public void updateBigDecimal(String columnLabel, BigDecimal x) throws SQLException { 511 | rs.updateBigDecimal(columnLabel, x); 512 | } 513 | 514 | @Override 515 | public void updateString(String columnLabel, String x) throws SQLException { 516 | rs.updateString(columnLabel, x); 517 | } 518 | 519 | @Override 520 | public void updateBytes(String columnLabel, byte[] x) throws SQLException { 521 | rs.updateBytes(columnLabel, x); 522 | } 523 | 524 | @Override 525 | public void updateDate(String columnLabel, Date x) throws SQLException { 526 | rs.updateDate(columnLabel, x); 527 | } 528 | 529 | @Override 530 | public void updateTime(String columnLabel, Time x) throws SQLException { 531 | rs.updateTime(columnLabel, x); 532 | } 533 | 534 | @Override 535 | public void updateTimestamp(String columnLabel, Timestamp x) throws SQLException { 536 | rs.updateTimestamp(columnLabel, x); 537 | } 538 | 539 | @Override 540 | public void updateAsciiStream(String columnLabel, InputStream x, int length) throws SQLException { 541 | rs.updateAsciiStream(columnLabel, x, length); 542 | } 543 | 544 | @Override 545 | public void updateBinaryStream(String columnLabel, InputStream x, int length) throws SQLException { 546 | rs.updateBinaryStream(columnLabel, x, length); 547 | } 548 | 549 | @Override 550 | public void updateCharacterStream(String columnLabel, Reader reader, int length) throws SQLException { 551 | rs.updateCharacterStream(columnLabel, reader, length); 552 | } 553 | 554 | @Override 555 | public void updateObject(String columnLabel, Object x, int scaleOrLength) throws SQLException { 556 | rs.updateObject(columnLabel, x, scaleOrLength); 557 | } 558 | 559 | @Override 560 | public void updateObject(String columnLabel, Object x) throws SQLException { 561 | rs.updateObject(columnLabel, x); 562 | } 563 | 564 | @Override 565 | public void insertRow() throws SQLException { 566 | rs.insertRow(); 567 | } 568 | 569 | @Override 570 | public void updateRow() throws SQLException { 571 | rs.updateRow(); 572 | } 573 | 574 | @Override 575 | public void deleteRow() throws SQLException { 576 | rs.deleteRow(); 577 | } 578 | 579 | @Override 580 | public void refreshRow() throws SQLException { 581 | rs.refreshRow(); 582 | } 583 | 584 | @Override 585 | public void cancelRowUpdates() throws SQLException { 586 | rs.cancelRowUpdates(); 587 | } 588 | 589 | @Override 590 | public void moveToInsertRow() throws SQLException { 591 | rs.moveToInsertRow(); 592 | } 593 | 594 | @Override 595 | public void moveToCurrentRow() throws SQLException { 596 | rs.moveToCurrentRow(); 597 | } 598 | 599 | @Override 600 | public Statement getStatement() throws SQLException { 601 | return s; 602 | } 603 | 604 | @Override 605 | public Object getObject(int columnIndex, Map> map) throws SQLException { 606 | return rs.getObject(columnIndex, map); 607 | } 608 | 609 | @Override 610 | public Ref getRef(int columnIndex) throws SQLException { 611 | return rs.getRef(columnIndex); 612 | } 613 | 614 | @Override 615 | public Blob getBlob(int columnIndex) throws SQLException { 616 | return rs.getBlob(columnIndex); 617 | } 618 | 619 | @Override 620 | public Clob getClob(int columnIndex) throws SQLException { 621 | return rs.getClob(columnIndex); 622 | } 623 | 624 | @Override 625 | public Array getArray(int columnIndex) throws SQLException { 626 | return rs.getArray(columnIndex); 627 | } 628 | 629 | @Override 630 | public Object getObject(String columnLabel, Map> map) throws SQLException { 631 | return rs.getObject(columnLabel, map); 632 | } 633 | 634 | @Override 635 | public Ref getRef(String columnLabel) throws SQLException { 636 | return rs.getRef(columnLabel); 637 | } 638 | 639 | @Override 640 | public Blob getBlob(String columnLabel) throws SQLException { 641 | return rs.getBlob(columnLabel); 642 | } 643 | 644 | @Override 645 | public Clob getClob(String columnLabel) throws SQLException { 646 | return rs.getClob(columnLabel); 647 | } 648 | 649 | @Override 650 | public Array getArray(String columnLabel) throws SQLException { 651 | return rs.getArray(columnLabel); 652 | } 653 | 654 | @Override 655 | public Date getDate(int columnIndex, Calendar cal) throws SQLException { 656 | return rs.getDate(columnIndex, cal); 657 | } 658 | 659 | @Override 660 | public Date getDate(String columnLabel, Calendar cal) throws SQLException { 661 | return rs.getDate(columnLabel, cal); 662 | } 663 | 664 | @Override 665 | public Time getTime(int columnIndex, Calendar cal) throws SQLException { 666 | return rs.getTime(columnIndex, cal); 667 | } 668 | 669 | @Override 670 | public Time getTime(String columnLabel, Calendar cal) throws SQLException { 671 | return rs.getTime(columnLabel, cal); 672 | } 673 | 674 | @Override 675 | public Timestamp getTimestamp(int columnIndex, Calendar cal) throws SQLException { 676 | return rs.getTimestamp(columnIndex, cal); 677 | } 678 | 679 | @Override 680 | public Timestamp getTimestamp(String columnLabel, Calendar cal) throws SQLException { 681 | return rs.getTimestamp(columnLabel, cal); 682 | } 683 | 684 | @Override 685 | public URL getURL(int columnIndex) throws SQLException { 686 | return rs.getURL(columnIndex); 687 | } 688 | 689 | @Override 690 | public URL getURL(String columnLabel) throws SQLException { 691 | return rs.getURL(columnLabel); 692 | } 693 | 694 | @Override 695 | public void updateRef(int columnIndex, Ref x) throws SQLException { 696 | rs.updateRef(columnIndex, x); 697 | } 698 | 699 | @Override 700 | public void updateRef(String columnLabel, Ref x) throws SQLException { 701 | rs.updateRef(columnLabel, x); 702 | } 703 | 704 | @Override 705 | public void updateBlob(int columnIndex, Blob x) throws SQLException { 706 | rs.updateBlob(columnIndex, x); 707 | } 708 | 709 | @Override 710 | public void updateBlob(String columnLabel, Blob x) throws SQLException { 711 | rs.updateBlob(columnLabel, x); 712 | } 713 | 714 | @Override 715 | public void updateClob(int columnIndex, Clob x) throws SQLException { 716 | rs.updateClob(columnIndex, x); 717 | } 718 | 719 | @Override 720 | public void updateClob(String columnLabel, Clob x) throws SQLException { 721 | rs.updateClob(columnLabel, x); 722 | } 723 | 724 | @Override 725 | public void updateArray(int columnIndex, Array x) throws SQLException { 726 | rs.updateArray(columnIndex, x); 727 | } 728 | 729 | @Override 730 | public void updateArray(String columnLabel, Array x) throws SQLException { 731 | rs.updateArray(columnLabel, x); 732 | } 733 | 734 | @Override 735 | public RowId getRowId(int columnIndex) throws SQLException { 736 | return rs.getRowId(columnIndex); 737 | } 738 | 739 | @Override 740 | public RowId getRowId(String columnLabel) throws SQLException { 741 | return rs.getRowId(columnLabel); 742 | } 743 | 744 | @Override 745 | public void updateRowId(int columnIndex, RowId x) throws SQLException { 746 | rs.updateRowId(columnIndex, x); 747 | } 748 | 749 | @Override 750 | public void updateRowId(String columnLabel, RowId x) throws SQLException { 751 | rs.updateRowId(columnLabel, x); 752 | } 753 | 754 | @Override 755 | public int getHoldability() throws SQLException { 756 | return rs.getHoldability(); 757 | } 758 | 759 | @Override 760 | public boolean isClosed() throws SQLException { 761 | return rs.isClosed(); 762 | } 763 | 764 | @Override 765 | public void updateNString(int columnIndex, String nString) throws SQLException { 766 | rs.updateNString(columnIndex, nString); 767 | } 768 | 769 | @Override 770 | public void updateNString(String columnLabel, String nString) throws SQLException { 771 | rs.updateNString(columnLabel, nString); 772 | } 773 | 774 | @Override 775 | public void updateNClob(int columnIndex, NClob nClob) throws SQLException { 776 | rs.updateNClob(columnIndex, nClob); 777 | } 778 | 779 | @Override 780 | public void updateNClob(String columnLabel, NClob nClob) throws SQLException { 781 | rs.updateNClob(columnLabel, nClob); 782 | } 783 | 784 | @Override 785 | public NClob getNClob(int columnIndex) throws SQLException { 786 | return rs.getNClob(columnIndex); 787 | } 788 | 789 | @Override 790 | public NClob getNClob(String columnLabel) throws SQLException { 791 | return rs.getNClob(columnLabel); 792 | } 793 | 794 | @Override 795 | public SQLXML getSQLXML(int columnIndex) throws SQLException { 796 | return rs.getSQLXML(columnIndex); 797 | } 798 | 799 | @Override 800 | public SQLXML getSQLXML(String columnLabel) throws SQLException { 801 | return rs.getSQLXML(columnLabel); 802 | } 803 | 804 | @Override 805 | public void updateSQLXML(int columnIndex, SQLXML xmlObject) throws SQLException { 806 | rs.updateSQLXML(columnIndex, xmlObject); 807 | } 808 | 809 | @Override 810 | public void updateSQLXML(String columnLabel, SQLXML xmlObject) throws SQLException { 811 | rs.updateSQLXML(columnLabel, xmlObject); 812 | } 813 | 814 | @Override 815 | public String getNString(int columnIndex) throws SQLException { 816 | return rs.getNString(columnIndex); 817 | } 818 | 819 | @Override 820 | public String getNString(String columnLabel) throws SQLException { 821 | return rs.getNString(columnLabel); 822 | } 823 | 824 | @Override 825 | public Reader getNCharacterStream(int columnIndex) throws SQLException { 826 | return rs.getNCharacterStream(columnIndex); 827 | } 828 | 829 | @Override 830 | public Reader getNCharacterStream(String columnLabel) throws SQLException { 831 | return rs.getNCharacterStream(columnLabel); 832 | } 833 | 834 | @Override 835 | public void updateNCharacterStream(int columnIndex, Reader x, long length) throws SQLException { 836 | rs.updateNCharacterStream(columnIndex, x, length); 837 | } 838 | 839 | @Override 840 | public void updateNCharacterStream(String columnLabel, Reader reader, long length) throws SQLException { 841 | rs.updateNCharacterStream(columnLabel, reader, length); 842 | } 843 | 844 | @Override 845 | public void updateAsciiStream(int columnIndex, InputStream x, long length) throws SQLException { 846 | rs.updateAsciiStream(columnIndex, x, length); 847 | } 848 | 849 | @Override 850 | public void updateBinaryStream(int columnIndex, InputStream x, long length) throws SQLException { 851 | rs.updateBinaryStream(columnIndex, x, length); 852 | } 853 | 854 | @Override 855 | public void updateCharacterStream(int columnIndex, Reader x, long length) throws SQLException { 856 | rs.updateCharacterStream(columnIndex, x, length); 857 | } 858 | 859 | @Override 860 | public void updateAsciiStream(String columnLabel, InputStream x, long length) throws SQLException { 861 | rs.updateAsciiStream(columnLabel, x, length); 862 | } 863 | 864 | @Override 865 | public void updateBinaryStream(String columnLabel, InputStream x, long length) throws SQLException { 866 | rs.updateBinaryStream(columnLabel, x, length); 867 | } 868 | 869 | @Override 870 | public void updateCharacterStream(String columnLabel, Reader reader, long length) throws SQLException { 871 | rs.updateCharacterStream(columnLabel, reader, length); 872 | } 873 | 874 | @Override 875 | public void updateBlob(int columnIndex, InputStream inputStream, long length) throws SQLException { 876 | rs.updateBlob(columnIndex, inputStream, length); 877 | } 878 | 879 | @Override 880 | public void updateBlob(String columnLabel, InputStream inputStream, long length) throws SQLException { 881 | rs.updateBlob(columnLabel, inputStream, length); 882 | } 883 | 884 | @Override 885 | public void updateClob(int columnIndex, Reader reader, long length) throws SQLException { 886 | rs.updateClob(columnIndex, reader, length); 887 | } 888 | 889 | @Override 890 | public void updateClob(String columnLabel, Reader reader, long length) throws SQLException { 891 | rs.updateClob(columnLabel, reader, length); 892 | } 893 | 894 | @Override 895 | public void updateNClob(int columnIndex, Reader reader, long length) throws SQLException { 896 | rs.updateNClob(columnIndex, reader, length); 897 | } 898 | 899 | @Override 900 | public void updateNClob(String columnLabel, Reader reader, long length) throws SQLException { 901 | rs.updateNClob(columnLabel, reader, length); 902 | } 903 | 904 | @Override 905 | public void updateNCharacterStream(int columnIndex, Reader x) throws SQLException { 906 | rs.updateNCharacterStream(columnIndex, x); 907 | } 908 | 909 | @Override 910 | public void updateNCharacterStream(String columnLabel, Reader reader) throws SQLException { 911 | rs.updateNCharacterStream(columnLabel, reader); 912 | } 913 | 914 | @Override 915 | public void updateAsciiStream(int columnIndex, InputStream x) throws SQLException { 916 | rs.updateAsciiStream(columnIndex, x); 917 | } 918 | 919 | @Override 920 | public void updateBinaryStream(int columnIndex, InputStream x) throws SQLException { 921 | rs.updateBinaryStream(columnIndex, x); 922 | } 923 | 924 | @Override 925 | public void updateCharacterStream(int columnIndex, Reader x) throws SQLException { 926 | rs.updateCharacterStream(columnIndex, x); 927 | } 928 | 929 | @Override 930 | public void updateAsciiStream(String columnLabel, InputStream x) throws SQLException { 931 | rs.updateAsciiStream(columnLabel, x); 932 | } 933 | 934 | @Override 935 | public void updateBinaryStream(String columnLabel, InputStream x) throws SQLException { 936 | rs.updateBinaryStream(columnLabel, x); 937 | } 938 | 939 | @Override 940 | public void updateCharacterStream(String columnLabel, Reader reader) throws SQLException { 941 | rs.updateCharacterStream(columnLabel, reader); 942 | } 943 | 944 | @Override 945 | public void updateBlob(int columnIndex, InputStream inputStream) throws SQLException { 946 | rs.updateBlob(columnIndex, inputStream); 947 | } 948 | 949 | @Override 950 | public void updateBlob(String columnLabel, InputStream inputStream) throws SQLException { 951 | rs.updateBlob(columnLabel, inputStream); 952 | } 953 | 954 | @Override 955 | public void updateClob(int columnIndex, Reader reader) throws SQLException { 956 | rs.updateClob(columnIndex, reader); 957 | } 958 | 959 | @Override 960 | public void updateClob(String columnLabel, Reader reader) throws SQLException { 961 | rs.updateClob(columnLabel, reader); 962 | } 963 | 964 | @Override 965 | public void updateNClob(int columnIndex, Reader reader) throws SQLException { 966 | rs.updateNClob(columnIndex, reader); 967 | } 968 | 969 | @Override 970 | public void updateNClob(String columnLabel, Reader reader) throws SQLException { 971 | rs.updateNClob(columnLabel, reader); 972 | } 973 | 974 | @Override 975 | public T getObject(int columnIndex, Class type) throws SQLException { 976 | return rs.getObject(columnIndex, type); 977 | } 978 | 979 | @Override 980 | public T getObject(String columnLabel, Class type) throws SQLException { 981 | return rs.getObject(columnLabel, type); 982 | } 983 | 984 | @Override 985 | public T unwrap(Class iface) throws SQLException { 986 | return rs.unwrap(iface); 987 | } 988 | 989 | @Override 990 | public boolean isWrapperFor(Class iface) throws SQLException { 991 | return rs.isWrapperFor(iface); 992 | } 993 | } 994 | -------------------------------------------------------------------------------- /jactiverecord/src/main/java/me/zzp/ar/pool/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright 2014 redraiment. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | 25 | /** 26 | * 提供数据库连接池和多线程相关的工具类。 27 | */ 28 | package me.zzp.ar.pool; 29 | -------------------------------------------------------------------------------- /jactiverecord/src/main/java/me/zzp/ar/sql/AbstractSqlBuilder.java: -------------------------------------------------------------------------------- 1 | package me.zzp.ar.sql; 2 | 3 | import java.util.Arrays; 4 | import java.util.Deque; 5 | import java.util.LinkedList; 6 | import me.zzp.util.Seq; 7 | 8 | /** 9 | * 实现生成insert、update、delete和select的语句的方法。 10 | * 由子类自行实现如何填充数据。 11 | * 12 | * @since 1.0 13 | * @author redraiment 14 | */ 15 | public abstract class AbstractSqlBuilder implements SqlBuilder { 16 | protected static enum Mode { 17 | Select, 18 | Insert, 19 | Update, 20 | Delete 21 | } 22 | 23 | protected Mode mode; 24 | protected Deque fields; 25 | protected Deque tables; 26 | protected Deque conditions; 27 | protected Deque groups; 28 | protected Deque having; 29 | protected Deque orders; 30 | protected int limit; 31 | protected int offset; 32 | 33 | protected SqlBuilder start(Mode mode) { 34 | this.mode = mode; 35 | fields = new LinkedList<>(); 36 | tables = new LinkedList<>(); 37 | conditions = new LinkedList<>(); 38 | groups = new LinkedList<>(); 39 | having = new LinkedList<>(); 40 | orders = new LinkedList<>(); 41 | limit = offset = -1; 42 | return this; 43 | } 44 | 45 | public SqlBuilder addField(String field) { 46 | fields.addLast(field); 47 | return this; 48 | } 49 | 50 | public SqlBuilder addTable(String table) { 51 | tables.addLast(table); 52 | return this; 53 | } 54 | 55 | public SqlBuilder addCondition(String condition) { 56 | conditions.addLast(condition); 57 | return this; 58 | } 59 | 60 | public SqlBuilder addGroup(String group) { 61 | groups.addLast(group); 62 | return this; 63 | } 64 | 65 | public SqlBuilder addHaving(String having) { 66 | this.having.addLast(having); 67 | return this; 68 | } 69 | 70 | public SqlBuilder addOrder(String order) { 71 | orders.addLast(order); 72 | return this; 73 | } 74 | 75 | public SqlBuilder setFields(String... fields) { 76 | this.fields.clear(); 77 | this.fields.addAll(Arrays.asList(fields)); 78 | return this; 79 | } 80 | 81 | public SqlBuilder setTables(String... tables) { 82 | this.tables.clear(); 83 | this.tables.addAll(Arrays.asList(tables)); 84 | return this; 85 | } 86 | 87 | public SqlBuilder setConditions(String... conditions) { 88 | this.conditions.clear(); 89 | this.conditions.addAll(Arrays.asList(conditions)); 90 | return this; 91 | } 92 | 93 | public SqlBuilder setGroups(String... groups) { 94 | this.groups.clear(); 95 | this.groups.addAll(Arrays.asList(groups)); 96 | return this; 97 | } 98 | 99 | public SqlBuilder setHaving(String... having) { 100 | this.having.clear(); 101 | this.having.addAll(Arrays.asList(having)); 102 | return this; 103 | } 104 | 105 | public SqlBuilder setOrders(String... orders) { 106 | this.orders.clear(); 107 | this.orders.addAll(Arrays.asList(orders)); 108 | return this; 109 | } 110 | 111 | public SqlBuilder setLimit(int limit) { 112 | this.limit = limit; 113 | return this; 114 | } 115 | 116 | public SqlBuilder setOffset(int offset) { 117 | this.offset = offset; 118 | return this; 119 | } 120 | 121 | // toString 122 | 123 | private String selectToString() { 124 | StringBuilder sql = new StringBuilder("select "); 125 | sql.append(Seq.join(fields, ", ")) 126 | .append(" from ") 127 | .append(Seq.join(tables, " join ")); 128 | if (!conditions.isEmpty()) { 129 | sql.append(" where ").append(Seq.join(conditions, " and ")); 130 | } 131 | if (!groups.isEmpty()) { 132 | sql.append(" group by ").append(Seq.join(groups, ", ")); 133 | if (!having.isEmpty()) { 134 | sql.append(" having ").append(Seq.join(having, " and ")); 135 | } 136 | } 137 | if (!orders.isEmpty()) { 138 | sql.append(" order by ").append(Seq.join(orders, ", ")); 139 | } 140 | if (limit > 0) { 141 | sql.append(" limit ").append(Integer.toString(limit)); 142 | } 143 | if (offset > -1) { 144 | sql.append(" offset ").append(Integer.toString(offset)); 145 | } 146 | 147 | return sql.toString(); 148 | } 149 | 150 | private String insertToString() { 151 | StringBuilder sql = new StringBuilder("insert into "); 152 | sql.append(tables.getFirst()) 153 | .append(" (") 154 | .append(Seq.join(fields, ", ")) 155 | .append(") values (") 156 | .append(Seq.join(Seq.map(fields, "?"), ", ")) 157 | .append(")"); 158 | return sql.toString(); 159 | } 160 | 161 | private String updateToString() { 162 | StringBuilder sql = new StringBuilder("update "); 163 | sql.append(tables.getFirst()) 164 | .append(" set ") 165 | .append(Seq.join(Seq.map(fields, "%s = ?"), ", ")); 166 | 167 | if (!conditions.isEmpty()) { 168 | sql.append(" where ").append(Seq.join(conditions, " and ")); 169 | } 170 | return sql.toString(); 171 | } 172 | 173 | private String deleteToString() { 174 | StringBuilder sql = new StringBuilder("delete from "); 175 | sql.append(tables.getFirst()); 176 | if (!conditions.isEmpty()) { 177 | sql.append(" where ").append(Seq.join(conditions, " and ")); 178 | } 179 | return sql.toString(); 180 | } 181 | 182 | @Override 183 | public String toString() { 184 | switch (mode) { 185 | case Select: return selectToString(); 186 | case Insert: return insertToString(); 187 | case Update: return updateToString(); 188 | case Delete: return deleteToString(); 189 | default: return ""; 190 | } 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /jactiverecord/src/main/java/me/zzp/ar/sql/SqlBuilder.java: -------------------------------------------------------------------------------- 1 | package me.zzp.ar.sql; 2 | 3 | /** 4 | * SQL语句构造器。 5 | * 6 | * @since 1.0 7 | * @author redraiment 8 | */ 9 | public interface SqlBuilder { 10 | public SqlBuilder insert(); 11 | public SqlBuilder into(String table); 12 | public SqlBuilder values(String... columns); 13 | 14 | public SqlBuilder update(String table); 15 | public SqlBuilder set(String... columns); 16 | 17 | public SqlBuilder select(String... columns); 18 | public SqlBuilder delete(); 19 | public SqlBuilder from(String table); 20 | 21 | public SqlBuilder join(String table); 22 | public SqlBuilder on(String... conditions); 23 | 24 | public SqlBuilder where(String... conditions); 25 | 26 | public SqlBuilder groupBy(String... columns); 27 | public SqlBuilder having(String... conditions); 28 | 29 | public SqlBuilder orderBy(String... columns); 30 | 31 | public SqlBuilder limit(int limit); 32 | public SqlBuilder offset(int offset); 33 | 34 | @Override 35 | public String toString(); 36 | } 37 | -------------------------------------------------------------------------------- /jactiverecord/src/main/java/me/zzp/ar/sql/SqlSyntaxException.java: -------------------------------------------------------------------------------- 1 | package me.zzp.ar.sql; 2 | 3 | /** 4 | * 拼装SQL时遇到任何问题,抛出此异常。 5 | * 6 | * @since 1.0 7 | * @author redraiment 8 | */ 9 | public class SqlSyntaxException extends RuntimeException { 10 | } 11 | -------------------------------------------------------------------------------- /jactiverecord/src/main/java/me/zzp/ar/sql/TSqlBuilder.java: -------------------------------------------------------------------------------- 1 | package me.zzp.ar.sql; 2 | 3 | import java.util.Arrays; 4 | import me.zzp.util.Seq; 5 | 6 | /** 7 | * TSQL构造器。 8 | * 9 | * @since 1.0 10 | * @author redraiment 11 | */ 12 | public class TSqlBuilder extends AbstractSqlBuilder { 13 | @Override 14 | public SqlBuilder insert() { 15 | return start(Mode.Insert); 16 | } 17 | 18 | @Override 19 | public SqlBuilder into(String table) { 20 | return setTables(table); 21 | } 22 | 23 | @Override 24 | public SqlBuilder values(String... columns) { 25 | return setFields(columns); 26 | } 27 | 28 | @Override 29 | public SqlBuilder update(String table) { 30 | start(Mode.Update); 31 | return setTables(table); 32 | } 33 | 34 | @Override 35 | public SqlBuilder set(String... columns) { 36 | return setFields(columns); 37 | } 38 | 39 | @Override 40 | public SqlBuilder select(String... columns) { 41 | start(Mode.Select); 42 | if (columns != null && columns.length > 0) { 43 | return setFields(columns); 44 | } else { 45 | return setFields("*"); 46 | } 47 | } 48 | 49 | @Override 50 | public SqlBuilder delete() { 51 | return start(Mode.Delete); 52 | } 53 | 54 | @Override 55 | public SqlBuilder from(String table) { 56 | return setTables(table); 57 | } 58 | 59 | @Override 60 | public SqlBuilder join(String table) { 61 | return addTable(table); 62 | } 63 | 64 | @Override 65 | public SqlBuilder on(String... conditions) { 66 | String table = tables.removeLast(); 67 | return addTable(table.concat(" on ").concat(Seq.join(Arrays.asList(conditions), "and"))); 68 | } 69 | 70 | @Override 71 | public SqlBuilder where(String... conditions) { 72 | return setConditions(conditions); 73 | } 74 | 75 | @Override 76 | public SqlBuilder groupBy(String... columns) { 77 | return setGroups(columns); 78 | } 79 | 80 | @Override 81 | public SqlBuilder having(String... conditions) { 82 | return setHaving(conditions); 83 | } 84 | 85 | @Override 86 | public SqlBuilder orderBy(String... columns) { 87 | return setOrders(columns); 88 | } 89 | 90 | @Override 91 | public SqlBuilder limit(int limit) { 92 | return setLimit(limit); 93 | } 94 | 95 | @Override 96 | public SqlBuilder offset(int offset) { 97 | return setOffset(offset); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /jactiverecord/src/main/java/me/zzp/ar/sql/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright 2014 redraiment. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | 25 | /** 26 | * 用构造器模式实现的SQL语句构造器。 27 | */ 28 | package me.zzp.ar.sql; 29 | -------------------------------------------------------------------------------- /jactiverecord/src/main/java/me/zzp/util/Seq.java: -------------------------------------------------------------------------------- 1 | package me.zzp.util; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Arrays; 5 | import java.util.Collection; 6 | import java.util.List; 7 | 8 | /** 9 | * 数组和List相关的工具方法。 10 | * 11 | * @since 1.0 12 | * @author redraiment 13 | */ 14 | public final class Seq { 15 | /** 16 | * 将一系列对象组装成数组。 17 | * 18 | * @param 元素类型。 19 | * @param args 多个同类型的元素。 20 | * @return 返回由这些元素组成的数组。 21 | */ 22 | public static E[] array(E... args) { 23 | return args; 24 | } 25 | 26 | /** 27 | * 将一系列对象组装成List。 28 | * 29 | * @param 元素类型。 30 | * @param args 多个同类型的元素。 31 | * @return 返回由这些元素组成的List。 32 | */ 33 | public static List list(E... args) { 34 | List list = new ArrayList<>(); 35 | list.addAll(Arrays.asList(args)); 36 | return list; 37 | } 38 | 39 | /** 40 | * 追加任意多个新int到int数组末尾。 41 | * 42 | * @param a 现有int数组。 43 | * @param b 任意多个新int。 44 | * @return 返回组装好的新int数组。 45 | */ 46 | public static int[] concat(int[] a, int... b) { 47 | int[] c = new int[a.length + b.length]; 48 | System.arraycopy(a, 0, c, 0, a.length); 49 | System.arraycopy(b, 0, c, a.length, b.length); 50 | return c; 51 | } 52 | 53 | /** 54 | * 追加任意多个新元素到数组末尾。 55 | * 56 | * @param 元素的类型。 57 | * @param a 现有数组。 58 | * @param b 任意多个新元素。 59 | * @return 返回组装好的新数组。 60 | */ 61 | public static E[] concat(E[] a, E... b) { 62 | return merge(a, b); 63 | } 64 | 65 | /** 66 | * 将数组连接成字符串。 67 | * 68 | * @param delimiter 分隔符。 69 | * @param args 任意多个元素。 70 | * @return 连接后的字符串。 71 | */ 72 | public static String join(String delimiter, Object... args) { 73 | return join(Arrays.asList(args), delimiter); 74 | } 75 | 76 | /** 77 | * 将容器里的元素连接成字符串。 78 | * 79 | * @param list 包含任意多个元素的容器。 80 | * @param delimiter 分隔符。 81 | * @return 连接后的字符串。 82 | */ 83 | public static String join(Collection list, String delimiter) { 84 | if (list == null || list.isEmpty()) { 85 | return ""; 86 | } 87 | if (delimiter == null) { 88 | delimiter = ""; 89 | } 90 | 91 | StringBuilder s = new StringBuilder(); 92 | boolean first = true; 93 | for (Object e : list) { 94 | if (first) { 95 | first = false; 96 | } else { 97 | s.append(delimiter); 98 | } 99 | s.append(e); 100 | } 101 | return s.toString(); 102 | } 103 | 104 | /** 105 | * 合并两个数组。 106 | * 107 | * @param 数组元素的类型。 108 | * @param a 前半部分数组。 109 | * @param b 后半部分数组。 110 | * @return 合并后的数组。 111 | */ 112 | public static E[] merge(E[] a, E[] b) { 113 | List list = merge(Arrays.asList(a), Arrays.asList(b)); 114 | return list.toArray(a); 115 | } 116 | 117 | /** 118 | * 合并两个List。 119 | * 120 | * @param List元素的类型。 121 | * @param a 前半部分List。 122 | * @param b 后半部分List。 123 | * @return 合并后的List。 124 | */ 125 | public static List merge(List a, List b) { 126 | List list = new ArrayList<>(); 127 | list.addAll(a); 128 | list.addAll(b); 129 | return list; 130 | } 131 | 132 | /** 133 | * 从数组中删除所有与给定对象equals的元素。 134 | * 135 | * @param 元素类型。 136 | * @param a 给定数组。 137 | * @param e 给定对象 138 | * @return 去除e之后的数组。 139 | */ 140 | public static E[] remove(E[] a, E e) { 141 | List list = remove(Arrays.asList(a), e); 142 | return (E[])list.toArray(); 143 | } 144 | 145 | /** 146 | * 从List中删除所有与给定对象equals的元素。 147 | * 148 | * @param 元素类型。 149 | * @param a 给定List。 150 | * @param e 给定对象 151 | * @return 去除e之后的List。 152 | */ 153 | public static List remove(List a, E e) { 154 | List list = new ArrayList<>(); 155 | for (E o : a) { 156 | if (!o.equals(e)) { 157 | list.add(o); 158 | } 159 | } 160 | return list; 161 | } 162 | 163 | /** 164 | * 根据给定的下标,选出多个元素,并组成新的数组。 165 | * 166 | * @param 元素类型。 167 | * @param a 给定数组。 168 | * @param indexes 任意多个要获取的元素下标。 169 | * @return 根据下标获得的元素组成的新数组。 170 | */ 171 | public static E[] valuesAt(E[] a, int... indexes) { 172 | List list = valuesAt(Arrays.asList(a), indexes); 173 | return (E[])list.toArray(); 174 | } 175 | 176 | /** 177 | * 根据给定的下标,选出多个元素,并组成新的List。 178 | * 179 | * @param 元素类型。 180 | * @param from 给定List。 181 | * @param indexes 任意多个要获取的元素下标。 182 | * @return 根据下标获得的元素组成的新List。 183 | */ 184 | public static List valuesAt(List from, int... indexes) { 185 | List list = new ArrayList<>(); 186 | for (int i : indexes) { 187 | if (0 <= i && i < from.size()) { 188 | list.add(from.get(i)); 189 | } else if (-from.size() <= i && i < 0) { 190 | list.add(from.get(from.size() + i)); 191 | } else { 192 | list.add(null); 193 | } 194 | } 195 | return list; 196 | } 197 | 198 | /** 199 | * 同时给int数组中多个位置同时赋值。 200 | * 201 | * @param a 给定int数组。 202 | * @param indexes 待赋值的位置下标。 203 | * @param values 与下标一一对应的int值。 204 | * @return 返回赋值后的新int数组。 205 | */ 206 | public static int[] assignAt(int[] a, Integer[] indexes, int... values) { 207 | if (indexes.length != values.length) { 208 | throw new IllegalArgumentException(String.format("index.length(%d) != values.length(%d)", indexes.length, values.length)); 209 | } 210 | for (int i = 0; i < indexes.length; i++) { 211 | int index = indexes[i]; 212 | if (0 <= index && index < a.length) { 213 | a[index] = values[i]; 214 | } else if (-a.length <= index && index < 0) { 215 | a[a.length + index] = values[i]; 216 | } else { 217 | throw new ArrayIndexOutOfBoundsException(index); 218 | } 219 | } 220 | return a; 221 | } 222 | 223 | /** 224 | * 同时给数组中多个位置同时赋值。 225 | * 226 | * @param 数组元素类型。 227 | * @param a 给定数组。 228 | * @param indexes 待赋值的位置下标。 229 | * @param values 与下标一一对应的值。 230 | * @return 返回赋值后的新数组。 231 | */ 232 | public static E[] assignAt(E[] a, Integer[] indexes, E... values) { 233 | if (indexes.length != values.length) { 234 | throw new IllegalArgumentException(String.format("index.length(%d) != values.length(%d)", indexes.length, values.length)); 235 | } 236 | for (int i = 0; i < indexes.length; i++) { 237 | int index = indexes[i]; 238 | if (0 <= index && index < a.length) { 239 | a[index] = values[i]; 240 | } else if (-a.length <= index && index < 0) { 241 | a[a.length + index] = values[i]; 242 | } else { 243 | throw new ArrayIndexOutOfBoundsException(index); 244 | } 245 | } 246 | return a; 247 | } 248 | 249 | /** 250 | * 根据规定格式,对容器中的每个元素进行格式化,并返回格式化后的结果。 251 | * 252 | * @param from 包含任意多个元素的容器。 253 | * @param format 格式化模板,与printf兼容。 254 | * @return 格式化后的新列表。 255 | */ 256 | public static List map(Collection from, String format) { 257 | List to = new ArrayList<>(from.size()); 258 | for (Object e : from) { 259 | to.add(String.format(format, e)); 260 | } 261 | return to; 262 | } 263 | 264 | /** 265 | * 拆分容器,每份至多包含n个元素,将每堆元素连接成一个独立字符串。 266 | * @param from 包含任意多个元素的容器。 267 | * @param n 子元素个数。 268 | * @param delimiter 分隔符。 269 | * @return 拆分后的字符串List。 270 | */ 271 | public static List partition(Collection from, int n, String delimiter) { 272 | List to = new ArrayList<>(); 273 | List buffer = new ArrayList<>(n); 274 | for (String e : from) { 275 | buffer.add(e); 276 | if (buffer.size() >= n) { 277 | to.add(join(buffer, delimiter)); 278 | buffer.clear(); 279 | } 280 | } 281 | return to; 282 | } 283 | } 284 | -------------------------------------------------------------------------------- /jactiverecord/src/main/java/me/zzp/util/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright 2014 redraiment. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | 25 | /** 26 | * 工具类集合。 27 | */ 28 | package me.zzp.util; 29 | -------------------------------------------------------------------------------- /jactiverecord/src/main/resources/META-INF/services/me.zzp.ar.d.Dialect: -------------------------------------------------------------------------------- 1 | me.zzp.ar.d.PostgreSQLDialect 2 | me.zzp.ar.d.HyperSQLDialect 3 | me.zzp.ar.d.SQLiteDialect 4 | me.zzp.ar.d.MySQLDialect -------------------------------------------------------------------------------- /jactiverecord/src/test/java/me/zzp/ar/sql/TSqlBuilderTest.java: -------------------------------------------------------------------------------- 1 | package me.zzp.ar.sql; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | import org.junit.BeforeClass; 5 | import org.junit.Test; 6 | 7 | public class TSqlBuilderTest { 8 | private static SqlBuilder sql = null; 9 | 10 | @BeforeClass 11 | public static void setUpClass() { 12 | sql = new TSqlBuilder(); 13 | } 14 | 15 | @Test 16 | public void testSelectAllFrom() { 17 | assertEquals("select * from users", sql.select().from("users").toString()); 18 | assertEquals("select * from users", sql.select("*").from("users").toString()); 19 | } 20 | 21 | @Test 22 | public void testSelectSomeFrom() { 23 | assertEquals("select name, age from users", sql.select("name", "age").from("users").toString()); 24 | } 25 | 26 | @Test 27 | public void testWhere() { 28 | sql.select().from("users").where("age > 17"); 29 | assertEquals("select * from users where age > 17", sql.toString()); 30 | sql.select().from("users").where("age >= 18 and age <= 25"); 31 | assertEquals("select * from users where age >= 18 and age <= 25", sql.toString()); 32 | } 33 | 34 | @Test 35 | public void testHaving() { 36 | sql.select("sum(age) as sum", "count(*) as count").from("users").groupBy("age").having("count(*) > 2"); 37 | assertEquals("select sum(age) as sum, count(*) as count from users group by age having count(*) > 2", sql.toString()); 38 | sql.select().from("users").having("count(*) > 2"); 39 | assertEquals("select * from users", sql.toString()); 40 | sql.select("age", "count(*)").from("users").groupBy("age"); 41 | assertEquals("select age, count(*) from users group by age", sql.toString()); 42 | } 43 | 44 | @Test 45 | public void testSort() { 46 | sql.select().from("users").orderBy("age"); 47 | assertEquals("select * from users order by age", sql.toString()); 48 | sql.select().from("users").orderBy("age asc"); 49 | assertEquals("select * from users order by age asc", sql.toString()); 50 | sql.select().from("users").orderBy("age desc"); 51 | assertEquals("select * from users order by age desc", sql.toString()); 52 | sql.select().from("users").orderBy("age", "name"); 53 | assertEquals("select * from users order by age, name", sql.toString()); 54 | } 55 | 56 | @Test 57 | public void testPaging() { 58 | sql.select().from("users").limit(10); 59 | assertEquals("select * from users limit 10", sql.toString()); 60 | sql.select().from("users").offset(10); 61 | assertEquals("select * from users offset 10", sql.toString()); 62 | sql.select().from("users").limit(10).offset(20); 63 | assertEquals("select * from users limit 10 offset 20", sql.toString()); 64 | sql.select().from("users").offset(20).limit(10); 65 | assertEquals("select * from users limit 10 offset 20", sql.toString()); 66 | } 67 | 68 | @Test 69 | public void testQuery() { 70 | assertEquals("select age, count(*) from users where id > 1 and age >= 18 group by age having count(*) > 2 order by age desc limit 10 offset 100", 71 | sql.select("age", "count(*)") 72 | .from("users") 73 | .where("id > 1", "age >= 18") 74 | .groupBy("age") 75 | .having("count(*) > 2") 76 | .orderBy("age desc") 77 | .limit(10) 78 | .offset(100) 79 | .toString()); 80 | } 81 | 82 | @Test 83 | public void testUpdate() { 84 | assertEquals("update users set age = ?", sql.update("users").set("age").toString()); 85 | assertEquals("update users set name = ?, age = ? where id = ?", sql.update("users").set("name", "age").where("id = ?").toString()); 86 | assertEquals("update users set age = ? where id > ? and name = ?", sql.update("users").set("age").where("id > ?", "name = ?").toString()); 87 | } 88 | 89 | @Test 90 | public void testDelete() { 91 | assertEquals("delete from users", sql.delete().from("users").toString()); 92 | assertEquals("delete from users where id > 2 and name = ?", sql.delete().from("users").where("id > 2", "name = ?").toString()); 93 | } 94 | 95 | @Test 96 | public void testInsert() { 97 | assertEquals("insert into users (name, age) values (?, ?)", 98 | sql.insert().into("users") 99 | .values("name", "age") 100 | .toString()); 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /jactiverecord/src/test/java/me/zzp/test/CompatibilityTest.java: -------------------------------------------------------------------------------- 1 | package me.zzp.test; 2 | 3 | import java.util.Iterator; 4 | import java.util.List; 5 | import me.zzp.ar.DB; 6 | import me.zzp.ar.Record; 7 | import me.zzp.ar.Table; 8 | import org.junit.Assert; 9 | import org.junit.Test; 10 | 11 | public class CompatibilityTest { 12 | 13 | private Table Zombie, Tweet, Comment, City, Relation; 14 | private DB dbo; 15 | 16 | public void setUpTables() { 17 | Zombie = dbo.createTable("zombies", "name varchar(64)"); 18 | City = dbo.createTable("cities", "name varchar(64)"); 19 | Tweet = dbo.createTable("tweets", "zombie_id int", "city_id int", "content varchar(64)"); 20 | Comment = dbo.createTable("comments", "zombie_id int", "tweet_id int", "content varchar(64)"); 21 | Relation = dbo.createTable("relations", "following int", "follower int"); 22 | } 23 | 24 | public void setUpAssociations() { 25 | Zombie.hasMany("tweets").by("zombie_id"); 26 | Zombie.hasAndBelongsToMany("travelled_cities").by("city_id").in("cities").through("tweets"); 27 | Zombie.hasMany("received_comments").by("tweet_id").in("comments").through("tweets"); 28 | Zombie.hasMany("send_comments").by("zombie_id").in("comments"); 29 | Zombie.hasMany("follower_relations").by("following").in("relations"); 30 | Zombie.hasAndBelongsToMany("followers").by("follower").in("zombies").through("follower_relations"); 31 | Zombie.hasMany("following_relations").by("follower").in("relations"); 32 | Zombie.hasAndBelongsToMany("followings").by("following").in("zombies").through("following_relations"); 33 | 34 | City.hasMany("tweets").by("city_id"); 35 | City.hasAndBelongsToMany("zombies").by("zombie_id").through("tweets"); 36 | 37 | Tweet.belongsTo("zombie").in("zombies"); 38 | Tweet.belongsTo("city").in("cities"); 39 | Tweet.hasMany("comments").by("tweet_id"); 40 | 41 | Comment.belongsTo("zombie").by("zombie_id").in("zombies"); 42 | Comment.belongsTo("tweet").by("tweet_id").in("tweets"); 43 | } 44 | 45 | public void setUpMetaData() { 46 | Record boston = City.create("name:", "Boston"); 47 | Record newyord = City.create("name:", "NewYork"); 48 | 49 | Record ash = Zombie.create("name:", "Ash"); 50 | Table ashTweets = ash.get("tweets"); 51 | Table ashTweetOnBoston = ashTweets.create("city_id:", boston.getInt("id"), "content:", "Hello Boston from Ash!").get("comments"); 52 | Table ashTweetOnNewYork = ashTweets.create("city_id:", newyord.getInt("id"), "content:", "Hello NewYord from Ash!").get("comments"); 53 | 54 | Record bob = Zombie.create("name:", "Bob"); 55 | Table bobTweets = bob.get("tweets"); 56 | Table bobTweetOnBoston = bobTweets.create("city_id:", boston.getInt("id"), "content:", "Hello Boston from Bob!").get("comments"); 57 | Table bobTweetOnNewYork = bobTweets.create("city_id:", newyord.getInt("id"), "content:", "Hello NewYord from Bob!").get("comments"); 58 | 59 | Record jim = Zombie.create("name:", "Jim"); 60 | Table jimTweets = jim.get("tweets"); 61 | Table jimTweetOnBoston = jimTweets.create("city_id:", boston.getInt("id"), "content:", "Hello Boston from Jim!").get("comments"); 62 | Table jimTweetOnNewYork = jimTweets.create("city_id:", newyord.getInt("id"), "content:", "Hello NewYord from Jim!").get("comments"); 63 | 64 | ashTweetOnBoston.create("zombie_id:", bob.getInt("id"), "content:", "Cool from Bob @ Boston"); 65 | ashTweetOnBoston.create("zombie_id:", jim.getInt("id"), "content:", "Cool from Jim @ Boston"); 66 | ashTweetOnNewYork.create("zombie_id:", bob.getInt("id"), "content:", "Cool from Bob @ NewYork"); 67 | ashTweetOnNewYork.create("zombie_id:", jim.getInt("id"), "content:", "Cool from Jim @ NewYork"); 68 | bobTweetOnBoston.create("zombie_id:", ash.getInt("id"), "content:", "Cool from Ash @ Boston"); 69 | bobTweetOnBoston.create("zombie_id:", jim.getInt("id"), "content:", "Cool from Jim @ Boston"); 70 | bobTweetOnNewYork.create("zombie_id:", ash.getInt("id"), "content:", "Cool from Ash @ NewYork"); 71 | bobTweetOnNewYork.create("zombie_id:", jim.getInt("id"), "content:", "Cool from Jim @ NewYork"); 72 | jimTweetOnBoston.create("zombie_id:", ash.getInt("id"), "content:", "Cool from Ash @ Boston"); 73 | jimTweetOnBoston.create("zombie_id:", bob.getInt("id"), "content:", "Cool from Bob @ Boston"); 74 | jimTweetOnNewYork.create("zombie_id:", ash.getInt("id"), "content:", "Cool from Ash @ NewYork"); 75 | jimTweetOnNewYork.create("zombie_id:", bob.getInt("id"), "content:", "Cool from Bob @ NewYork"); 76 | 77 | Relation.create("following:", ash.getInt("id"), "follower:", bob.getInt("id")); 78 | Relation.create("following:", ash.getInt("id"), "follower:", jim.getInt("id")); 79 | Relation.create("following:", bob.getInt("id"), "follower:", ash.getInt("id")); 80 | Relation.create("following:", bob.getInt("id"), "follower:", bob.getInt("id")); 81 | Relation.create("following:", jim.getInt("id"), "follower:", ash.getInt("id")); 82 | Relation.create("following:", jim.getInt("id"), "follower:", jim.getInt("id")); 83 | } 84 | 85 | public void setUp() { 86 | setUpTables(); 87 | setUpAssociations(); 88 | setUpMetaData(); 89 | } 90 | 91 | public void validateCreate() { 92 | Assert.assertEquals(5, dbo.getTableNames().size()); 93 | 94 | String[] cityNames = new String[] {"Boston", "NewYork"}; 95 | List cities = City.all(); 96 | Assert.assertEquals(cityNames.length, cities.size()); 97 | for (int i = 0; i < cityNames.length; i++) 98 | Assert.assertEquals(cityNames[i], cities.get(i).getStr("name")); 99 | 100 | String[] zombieNames = new String[] {"Ash", "Bob", "Jim"}; 101 | List zombies = Zombie.all(); 102 | Assert.assertEquals(zombieNames.length, zombies.size()); 103 | for (int i = 0; i < zombieNames.length; i++) 104 | Assert.assertEquals(zombieNames[i], zombies.get(i).getStr("name")); 105 | 106 | List tweets = Tweet.all(); 107 | Assert.assertEquals(cityNames.length * zombieNames.length, tweets.size()); 108 | Iterator tweet = tweets.iterator(); 109 | for (String zombie : zombieNames) { 110 | for (String city : cityNames) { 111 | Assert.assertEquals(String.format("Hello %s from %s!", city, zombie), tweet.next().getStr("content")); 112 | } 113 | } 114 | 115 | List comments = Comment.all(); 116 | Assert.assertEquals((zombieNames.length - 1) * cityNames.length * zombieNames.length, comments.size()); 117 | Iterator comment = comments.iterator(); 118 | for (String zombie : zombieNames) { 119 | for (String city : cityNames) { 120 | for (String friend : zombieNames) { 121 | if (!zombie.equals(friend)) { 122 | Assert.assertEquals(String.format("Cool from %s @ %s", friend, city), comment.next().getStr("content")); 123 | } 124 | } 125 | } 126 | } 127 | 128 | List relations = Relation.all(); 129 | Iterator relation = relations.iterator(); 130 | for (int zombie = 0; zombie < zombieNames.length; zombie++) { 131 | for (int friend = 0; friend < zombieNames.length; friend++) { 132 | if (zombie != friend) { 133 | Record e = relation.next(); 134 | Assert.assertEquals(zombie, e.getInt("following")); 135 | Assert.assertEquals(friend, e.getInt("follower")); 136 | } 137 | } 138 | } 139 | } 140 | 141 | public void validateRelation() { 142 | for (Record zombie : Zombie.all()) { 143 | Assert.assertEquals(2, zombie.get("tweets", Table.class).all().size()); 144 | Assert.assertEquals(2, zombie.get("travelled_cities", Table.class).all().size()); 145 | Assert.assertEquals(4, zombie.get("received_comments", Table.class).all().size()); 146 | Assert.assertEquals(4, zombie.get("send_comments", Table.class).all().size()); 147 | Assert.assertEquals(2, zombie.get("follower_relations", Table.class).all().size()); 148 | Assert.assertEquals(2, zombie.get("followers", Table.class).all().size()); 149 | Assert.assertEquals(2, zombie.get("following_relations", Table.class).all().size()); 150 | Assert.assertEquals(2, zombie.get("followings", Table.class).all().size()); 151 | } 152 | 153 | for (Record city : City.all()) { 154 | Assert.assertEquals(3, city.get("tweets", Table.class).all().size()); 155 | Assert.assertEquals(3, city.get("zombies", Table.class).all().size()); 156 | } 157 | 158 | for (Record tweet : Tweet.all()) { 159 | try { 160 | Assert.assertNotNull(tweet.get("zombie")); 161 | Assert.assertNotNull(tweet.get("city")); 162 | } catch (Throwable e) { 163 | Assert.fail(e.getMessage()); 164 | } 165 | Assert.assertEquals(2, tweet.get("comments", Table.class).all().size()); 166 | } 167 | 168 | for (Record comment : Comment.all()) { 169 | try { 170 | Assert.assertNotNull(comment.get("zombie")); 171 | Assert.assertNotNull(comment.get("tweet")); 172 | } catch (Throwable e) { 173 | Assert.fail(e.getMessage()); 174 | } 175 | } 176 | } 177 | 178 | public void valiateQuery() { 179 | Assert.assertEquals(1, Tweet.first().getInt("id")); 180 | Assert.assertEquals(12, Tweet.last().getInt("id")); 181 | List list = Tweet.paging(2, 4); 182 | Assert.assertEquals(4, list.size()); 183 | Assert.assertEquals(9, list.get(0).getInt("id")); 184 | Assert.assertEquals(10, list.get(1).getInt("id")); 185 | Assert.assertEquals(11, list.get(2).getInt("id")); 186 | Assert.assertEquals(12, list.get(3).getInt("id")); 187 | } 188 | 189 | public void validate() { 190 | validateCreate(); 191 | validateRelation(); 192 | valiateQuery(); 193 | } 194 | 195 | public void test(DB dbo) { 196 | this.dbo = dbo; 197 | 198 | try { 199 | setUp(); 200 | } catch (Throwable e) { 201 | Assert.fail(e.getMessage()); 202 | } finally { 203 | tearDown(); 204 | } 205 | } 206 | 207 | @Test 208 | public void sqlite3() { 209 | test(DB.open("jdbc:sqlite::memory:")); 210 | } 211 | 212 | @Test 213 | public void mysql() { 214 | //test(DB.open("jdbc:mysql://localhost/weibo", "redraiment", "")); 215 | } 216 | 217 | @Test 218 | public void postgresql() { 219 | //test(DB.open("jdbc:postgresql://localhost/weibo", "postgres", "123456")); 220 | } 221 | 222 | @Test 223 | public void hypersql() { 224 | test(DB.open("jdbc:hsqldb:mem:weibo", "sa", "")); 225 | } 226 | 227 | public void tearDown() { 228 | if (dbo != null) { 229 | dbo = null; 230 | } 231 | } 232 | } 233 | -------------------------------------------------------------------------------- /jactiverecord/src/test/java/me/zzp/test/CountActiveConnection.java: -------------------------------------------------------------------------------- 1 | package me.zzp.test; 2 | 3 | import java.util.Scanner; 4 | import me.zzp.ar.DB; 5 | import me.zzp.ar.Table; 6 | import me.zzp.ar.pool.JdbcDataSource; 7 | 8 | public class CountActiveConnection { 9 | public static void main(String[] args) throws InterruptedException { 10 | DB dbo = DB.open(new JdbcDataSource("jdbc:postgresql:code", "postgres", "123")); 11 | final Table People = dbo.active("person"); 12 | for (int i = 0; i < 10; i++) { 13 | Thread th = new Thread(new Runnable() { 14 | @Override 15 | public void run() { 16 | People.first(); 17 | } 18 | }); 19 | th.start(); 20 | th.join(); 21 | } 22 | System.out.println("pause"); 23 | Scanner cin = new Scanner(System.in); 24 | cin.next(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /jactiverecord/src/test/java/me/zzp/test/TestSuite.java: -------------------------------------------------------------------------------- 1 | package me.zzp.test; 2 | 3 | import me.zzp.ar.sql.TSqlBuilderTest; 4 | import me.zzp.util.SeqTest; 5 | import org.junit.runner.RunWith; 6 | import org.junit.runners.Suite; 7 | 8 | @RunWith(Suite.class) 9 | @Suite.SuiteClasses({ 10 | SeqTest.class, 11 | TSqlBuilderTest.class 12 | }) 13 | public class TestSuite { 14 | } 15 | -------------------------------------------------------------------------------- /jactiverecord/src/test/java/me/zzp/test/TransactionTest.java: -------------------------------------------------------------------------------- 1 | package me.zzp.test; 2 | 3 | import me.zzp.ar.DB; 4 | import me.zzp.ar.Record; 5 | import me.zzp.ar.Table; 6 | import org.junit.After; 7 | import org.junit.Assert; 8 | import org.junit.Before; 9 | import org.junit.Test; 10 | 11 | public class TransactionTest { 12 | 13 | private DB dbo; 14 | 15 | @Before 16 | public void setUp() { 17 | dbo = DB.open("jdbc:sqlite::memory:"); 18 | dbo.createTable("tweets", "zombie_id int", "content varchar(64) not null unique"); 19 | Table Zombie = dbo.createTable("zombies", "name varchar(64)"); 20 | Zombie.hasMany("tweets").by("zombie_id"); 21 | } 22 | 23 | @After 24 | public void tearDown() { 25 | if (dbo != null) { 26 | dbo = null; 27 | } 28 | } 29 | 30 | @Test 31 | public void testTx() { 32 | final Table Zombie = dbo.active("zombies"); 33 | Table Tweet = dbo.active("tweets"); 34 | 35 | dbo.tx(new Runnable() { 36 | @Override 37 | public void run() { 38 | Record ash = Zombie.create("name:", "Ash"); 39 | Table tweets = ash.get("tweets"); 40 | tweets.create("content:", "Hello world"); 41 | } 42 | }); 43 | Assert.assertEquals(1, Zombie.all().size()); 44 | Assert.assertEquals(1, Tweet.all().size()); 45 | 46 | Assert.assertFalse(dbo.tx(new Runnable() { 47 | @Override 48 | public void run() { 49 | Record bob = Zombie.create("name:", "Bob"); 50 | Table tweets = bob.get("tweets"); 51 | tweets.create("content:", "Hello world"); 52 | } 53 | })); 54 | Assert.assertEquals(1, Zombie.all().size()); 55 | Assert.assertEquals(1, Tweet.all().size()); 56 | 57 | Record ash = Zombie.find(1); 58 | Table tweets = ash.get("tweets"); 59 | tweets.create("content:", "Hello ash"); 60 | Assert.assertEquals(1, Zombie.all().size()); 61 | Assert.assertEquals(2, Tweet.all().size()); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /jactiverecord/src/test/java/me/zzp/util/SeqTest.java: -------------------------------------------------------------------------------- 1 | package me.zzp.util; 2 | 3 | import java.util.Arrays; 4 | import java.util.Collections; 5 | import org.junit.Assert; 6 | import org.junit.Test; 7 | 8 | public class SeqTest { 9 | @Test 10 | public void testConcat() { 11 | Assert.assertArrayEquals(Seq.array("1", "2", "3", "4"), Seq.concat(Seq.array("1", "2"), "3", "4")); 12 | } 13 | 14 | @Test 15 | public void testRemove() { 16 | Assert.assertArrayEquals(Seq.array("1", "2", "4"), Seq.remove(Seq.array("1", "3", "2", "3", "3", "4"), "3")); 17 | } 18 | 19 | @Test 20 | public void testJoinListNull() { 21 | Assert.assertEquals("", Seq.join(null, "")); 22 | } 23 | 24 | @Test 25 | public void testJoinListEmpty() { 26 | Assert.assertEquals("", Seq.join(Collections.EMPTY_LIST, "")); 27 | } 28 | 29 | @Test 30 | public void testJoinDelimiterNull() { 31 | Assert.assertEquals("123", Seq.join(Arrays.asList("1", "2", "3"), null)); 32 | } 33 | 34 | @Test 35 | public void testJoinDelimiterEmpty() { 36 | Assert.assertEquals("123", Seq.join(Arrays.asList("1", "2", "3"), "")); 37 | } 38 | 39 | @Test 40 | public void testCommaList() { 41 | Assert.assertEquals("1, 2, 3", Seq.join(Arrays.asList("1", "2", "3"), ", ")); 42 | } 43 | 44 | @Test 45 | public void testConditionList() { 46 | Assert.assertEquals("username = ? and rowid = ? and password = ?", Seq.join(Arrays.asList("username = ?", "rowid = ?", "password = ?"), " and ")); 47 | } 48 | 49 | @Test 50 | public void testConstantMap() { 51 | String[] actuals = Seq.map(Arrays.asList("1", "2", "3"), "?").toArray(new String[0]); 52 | Assert.assertArrayEquals(new String[] {"?", "?", "?"}, actuals); 53 | } 54 | 55 | @Test 56 | public void testFormatMap() { 57 | String[] actuals = Seq.map(Arrays.asList("1", "2", "3"), "id = %s").toArray(new String[0]); 58 | Assert.assertArrayEquals(new String[] {"id = 1", "id = 2", "id = 3"}, actuals); 59 | } 60 | 61 | @Test 62 | public void testPartition() { 63 | String[] actuals = null; 64 | 65 | actuals = Seq.partition(Arrays.asList("1", "2", "3", "4"), 2, " ").toArray(new String[0]); 66 | Assert.assertArrayEquals(new String[] {"1 2", "3 4"}, actuals); 67 | 68 | actuals = Seq.partition(Arrays.asList("1", "2", "3", "4", "5"), 2, " ").toArray(new String[0]); 69 | Assert.assertArrayEquals(new String[] {"1 2", "3 4"}, actuals); 70 | 71 | actuals = Seq.partition(Arrays.asList("1", "2", "3", "4", "5", "6"), 3, " ").toArray(new String[0]); 72 | Assert.assertArrayEquals(new String[] {"1 2 3", "4 5 6"}, actuals); 73 | } 74 | 75 | @Test 76 | public void testAssignAt() { 77 | int[] a = new int[5]; 78 | Seq.assignAt(a, Seq.array(0, 2, 3), 1, 3, 4); 79 | Seq.assignAt(a, Seq.array(-1, -4), 5, 2); 80 | Assert.assertArrayEquals(new int[] {1, 2, 3, 4, 5}, a); 81 | 82 | String[] s = new String[3]; 83 | Seq.assignAt(s, Seq.array(0, 2), "a", "d"); 84 | Seq.assignAt(s, Seq.array(-2, -1), "b", "c"); 85 | Assert.assertArrayEquals(Seq.array("a", "b", "c"), s); 86 | } 87 | } -------------------------------------------------------------------------------- /jactiverecord/src/test/resources/META-INF/services/java.sql.Driver: -------------------------------------------------------------------------------- 1 | org.sqlite.JDBC -------------------------------------------------------------------------------- /java-on-rails/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | me.zzp 5 | raiment 6 | 1.0 7 | jar 8 | 9 | 10 | ${project.groupId} 11 | jactionview 12 | ${project.version} 13 | 14 | 15 | ${project.groupId} 16 | jactioncontroller 17 | ${project.version} 18 | 19 | 20 | ${project.groupId} 21 | jactiverecord-el 22 | 1.2 23 | 24 | 25 | 26 | UTF-8 27 | 1.7 28 | 1.7 29 | 30 | -------------------------------------------------------------------------------- /java-on-rails/src/main/resources/META-INF/web-fragment.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | jactionview-paths 5 | /views 6 | 7 | 8 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | me.zzp 8 | java-on-rails 9 | 1.0.0-SNAPSHOT 10 | pom 11 | 12 | https://github.com/redraiment/java-on-rails 13 | 14 | 15 | 16 | redraiment 17 | Zhang, Zepeng 18 | redraiment@gmail.com 19 | http://zzp.me 20 | 21 | 22 | 23 | 24 | 25 | MIT License 26 | http://www.opensource.org/licenses/mit-license.php 27 | 28 | 29 | 30 | 31 | scm:git:git@github.com:redraiment/java-on-rails.git 32 | scm:git:git@github.com:redraiment/java-on-rails.git 33 | git@github.com:redraiment/java-on-rails.git 34 | 35 | 36 | 37 | jactioncontroller 38 | jactionview 39 | jactiverecord 40 | jactiverecord-el 41 | 42 | 43 | 44 | UTF-8 45 | 8 46 | 8 47 | 48 | 49 | 50 | 51 | 52 | me.zzp 53 | jactioncontroller 54 | 1.0.0-SNAPSHOT 55 | 56 | 57 | me.zzp 58 | jactionview 59 | 1.0.0-SNAPSHOT 60 | 61 | 62 | me.zzp 63 | jactiverecord 64 | 2.3 65 | 66 | 67 | me.zzp 68 | jactiverecord-el 69 | 1.2 70 | 71 | 72 | 73 | jakarta.servlet 74 | jakarta.servlet-api 75 | 6.0.0 76 | 77 | 78 | com.h2database 79 | h2 80 | 2.1.214 81 | 82 | 83 | org.xerial 84 | sqlite-jdbc 85 | 3.39.3.0 86 | 87 | 88 | org.postgresql 89 | postgresql 90 | 42.5.0 91 | 92 | 93 | mysql 94 | mysql-connector-java 95 | 8.0.30 96 | 97 | 98 | 99 | org.junit 100 | junit-bom 101 | 5.9.0 102 | pom 103 | import 104 | 105 | 106 | 107 | 108 | 109 | 110 | sonatype-nexus-snapshots 111 | Sonatype Nexus snapshot repository 112 | https://oss.sonatype.org/content/repositories/snapshots 113 | 114 | 115 | sonatype-nexus-staging 116 | Sonatype Nexus release repository 117 | https://oss.sonatype.org/service/local/staging/deploy/maven2/ 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | org.apache.maven.plugins 127 | maven-source-plugin 128 | 3.2.1 129 | 130 | 131 | package 132 | 133 | jar-no-fork 134 | 135 | 136 | 137 | 138 | 139 | 140 | org.apache.maven.plugins 141 | maven-javadoc-plugin 142 | 3.4.1 143 | 144 | 145 | package 146 | 147 | jar 148 | 149 | 150 | 151 | 152 | 153 | 154 | org.apache.maven.plugins 155 | maven-gpg-plugin 156 | 3.0.1 157 | 158 | 159 | verify 160 | 161 | sign 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | --------------------------------------------------------------------------------