otter支持同步到mycat么?
搭了个环境,测试了一下发现,如果目标数据源是mycat,会有如下异常,无法同步:
pid:1 nid:1 exception:setl:com.alibaba.otter.node.etl.select.exceptions.SelectException: com.google.common.collect.ComputationException: org.springframework.jdbc.CannotGetJdbcConnectionException: Could not get JDBC Connection; nested exception is org.apache.commons.dbcp.SQLNestedException: Cannot create PoolableConnectionFactory (ResultSet is from UPDATE. No Data.) at com.alibaba.otter.node.etl.select.selector.MessageParser.parse(MessageParser.java:211) at com.alibaba.otter.node.etl.select.selector.canal.CanalEmbedSelector.selector(CanalEmbedSelector.java:258) at com.alibaba.otter.node.etl.select.SelectTask.processSelect(SelectTask.java:236) at com.alibaba.otter.node.etl.select.SelectTask.access$300(SelectTask.java:94) at com.alibaba.otter.node.etl.select.SelectTask$1.run(SelectTask.java:208) at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511) at java.util.concurrent.FutureTask.run(FutureTask.java:266) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) at java.lang.Thread.run(Thread.java:748) Caused by: com.google.common.collect.ComputationException: org.springframework.jdbc.CannotGetJdbcConnectionException: Could not get JDBC Connection; nested exception is org.apache.commons.dbcp.SQLNestedException: Cannot create PoolableConnectionFactory (ResultSet is from UPDATE. No Data.) at com.google.common.collect.ComputingConcurrentHashMap$ComputingSegment.compute(ComputingConcurrentHashMap.java:167) at com.google.common.collect.ComputingConcurrentHashMap$ComputingSegment.compute(ComputingConcurrentHashMap.java:116) at com.google.common.collect.ComputingConcurrentHashMap.apply(ComputingConcurrentHashMap.java:67) at com.google.common.collect.MapMaker$ComputingMapAdapter.get(MapMaker.java:623) at com.alibaba.otter.node.etl.common.db.dialect.DbDialectFactory.getDbDialect(DbDialectFactory.java:117) at com.alibaba.otter.node.etl.select.selector.MessageParser.internParse(MessageParser.java:442) at com.alibaba.otter.node.etl.select.selector.MessageParser.internParse(MessageParser.java:390) at com.alibaba.otter.node.etl.select.selector.MessageParser.parse(MessageParser.java:141) ... 9 more Caused by: org.springframework.jdbc.CannotGetJdbcConnectionException: Could not get JDBC Connection; nested exception is org.apache.commons.dbcp.SQLNestedException: Cannot create PoolableConnectionFactory (ResultSet is from UPDATE. No Data.) at org.springframework.jdbc.datasource.DataSourceUtils.getConnection(DataSourceUtils.java:80) at org.springframework.jdbc.core.JdbcTemplate.execute(JdbcTemplate.java:331) at com.alibaba.otter.node.etl.common.db.dialect.DbDialectFactory$2$1.apply(DbDialectFactory.java:81) at com.alibaba.otter.node.etl.common.db.dialect.DbDialectFactory$2$1.apply(DbDialectFactory.java:76) at com.google.common.collect.ComputingConcurrentHashMap$ComputingSegment.compute(ComputingConcurrentHashMap.java:155) ... 16 more Caused by: org.apache.commons.dbcp.SQLNestedException: Cannot create PoolableConnectionFactory (ResultSet is from UPDATE. No Data.) at org.apache.commons.dbcp.BasicDataSource.createPoolableConnectionFactory(BasicDataSource.java:1549) at org.apache.commons.dbcp.BasicDataSource.createDataSource(BasicDataSource.java:1388) at org.apache.commons.dbcp.BasicDataSource.getConnection(BasicDataSource.java:1044) at org.springframework.jdbc.datasource.DataSourceUtils.doGetConnection(DataSourceUtils.java:111) at org.springframework.jdbc.datasource.DataSourceUtils.getConnection(DataSourceUtils.java:77) ... 20 more Caused by: java.sql.SQLException: ResultSet is from UPDATE. No Data. at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:998) at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:937) at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:926) at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:872) at com.mysql.jdbc.ResultSetImpl.next(ResultSetImpl.java:6327) at org.apache.commons.dbcp.DelegatingResultSet.next(DelegatingResultSet.java:207) at org.apache.commons.dbcp.PoolableConnectionFactory.validateConnection(PoolableConnectionFactory.java:659) at org.apache.commons.dbcp.BasicDataSource.validateConnectionFactory(BasicDataSource.java:1558) at org.apache.commons.dbcp.BasicDataSource.createPoolableConnectionFactory(BasicDataSource.java:1545) ... 24 more
原提问者GitHub用户 afareg
带你读《2022技术人的百宝黑皮书》——大淘宝技术数据模型治理阶段性分享(1)
作者:剑萧出品:大淘宝技术大淘宝技术数据体系经过多年发展,通过丰富的数据和产品支撑了复杂的业务场景,在数据领域取得了非常大的领先优势。随着数据规模越来越大,开发人员越来越多,虽有阿里大数据体系规范进行统一管理,但是由于没有在产品侧进行有效的模型设计和管控,在模型规范性、应用层效率、通用层复用性等方面的问题逐渐凸显。计存成本提升、效率降低、规范减弱、数据使用难度变大、运维负担增加等。为了解决这些问题,我们进行了大淘宝技术模型治理专项,在数据服务业务的同时,追求极致的降本提效目标。数据现状为了更好的分析当前大淘宝的数据问题,我们进行了详细的数据分析,首先进行数字化。(整个问题分析有详细的数据支撑,涉及到数据安全,因此只抽象问题,不展示具体数据细节)。规范性问题表命名不规范,缺乏管控:随着数据量增长,大淘宝的表出现了大量命名未遵循阿里大数据体系的情况,难以管控。通用层复用性问题通用层表复用性不高:通用层表下游引用少于2个的数量非常多;通用层建设不足或通用层透出不足:cdm引用下降,ads引用上升;较多的ads表共性逻辑未下沉:出现很多ads表代码重复,字段相似度高的情况;应用层效率问题临时表多,影响数据管理:出现了很多TDDL临时表、PAI临时表、机器临时表、压测临时表等;663通用层表在各团队分布不合理:散布多个团队;较多的ads表共性逻辑未下沉;部分ads表层内依赖深度较深:很多ads表在应用层的深度超过10层;应用层跨集市依赖问题明显:不同集市间ads互相依赖,不仅影响了数据稳定性,而且数据准确性也难以保障;存在大量的可交接的通用层表:不同团队的通用层数据与大淘宝数据混合在一起;表人员分配不均衡:表owner管理的表数量分布很不均匀,有些owner名下只有几十张,有些owner名下有几千张;带你读《2022技术人的百宝黑皮书》——大淘宝技术数据模型治理阶段性分享(2) https://developer.aliyun.com/article/1247085?groupCode=taobaotech
异常定义: 程序在运行时出现不正常情况。 问题也是现实生活中的一个具体事物,可以通过java类的形式进行描述。并封装成对象。Java对不正常情况进行描述后的对象体现。 问题划分:严重/非严重。 严重的:java通过Error类描述。 Error一般不编写针对性的代码进行处理。 非严重的:java通过Exception类进行描述。 对于Exception可以使用针对性的处理方式进行处理。 常见的异常:数组下标越界、空指针...... 常见的错误:内存溢出OutofmemoryError Error或Exception都有一些共性内容,抽取出来成为Throwable类。 Throwable |--Error |--Exception |--RuntimeException自定义异常: 必须是自定义类继承Exception,或者Throwable,或者Error。 原因:异常体系特点:异常类和异常对象都需要被抛出。这个可抛性是Throwable这个体系独有特点。throw和throws的区别: throws使用在函数上,throw使用在函数内。 throws后面跟的异常类。可以跟多个,用逗号隔开。 throw后边跟的是异常对象。对多异常的处理: 1、声明异常时,越具体越好。 2、多个catch块儿,父类的catch放到最下面。Exception中特殊的子类异常运行时异常: 如果在函数内抛出该异常,函数上可以不用声明,编译可以通过。 如果在函数上声明了该异常。调用者可以不用进行处理,编译可以通过。 之所以不用在函数声明,是因为不需要让调用者处理。 当该异常发生,希望程序停止。因为在运行时,出现了无法继续运算的情况,希望停止程序后,对代码进行修正。 如果该异常的发生,无法再继续进行运算,就让自定义异常继承RuntimeException。异常分两种: 1、编译时被检测的异常。 2、编译时不被检测的异常(运行时异常。RuntimeException及其子类) 编译时被检测的异常是可处理的,需要声明出来,被调用者处理。调用者用try catch去处理。 编译时不被检测的异常可只声明或只在内部抛出。 throw、return都是函数结束标识。它们后边的代码都不能执行。Finally: finally代码块:定义一定执行的代码。通常用于关闭资源,比如数据库连接。异常的好处: 1、将问题进行封装。 2、将正常流程代码和问题处理代码相分离,方便于阅读。总结: 异常处理,多多实践。
Java EE 13个规范
学完了j2ee视频,对java有了一个模糊的认识。Java EE,Java平台企业版(Java Platform enterprise edition),由Sun公司为企业级应用推出的标准平台。共三个版本:Java EE(enterprise edition,企业版)、Java SE(standard edition,标准版)、Java ME(micro edition,微型版)。后来出了jdk1.2版本的时候,名字改成了j2se、j2ee、j2me。所以我们学的j2ee是2005年6月之前的叫法。05年6月之后,就把2去掉了,重新更名为开始的名字。所以java ee和j2ee是这样的一个关系。java ee是一种标准平台,大家开发软件都采用这样的标准来开发。在学视频的过程中是一个规范一个规范挨着学的。学的时候并不知道它们叫规范,组合起来合成了十三个规范。下面了解一下。1、JDBC(Java Database Connectivity)jdbc,用于数据库的链接。jdbc对数据库的访问具有平台无关性。2、JNDI(Java Name and Directory Interface)jndi用于管理资源,如本地文件系统。3、EJB(Enterprise JavaBean)视频中大部分内容都在介绍EJB。业务逻辑部分的代码写在这里。4、RMI(Remote Method Invoke)远程调用协议:RMI远程方法调用,跟电脑的远程连接似的,发起远程的一段是客户端,被远程的一端是服务端。RMI使用了序列化方式在客户端和服务端传递数据。5、Java IDL/CORBA6、JSP(Java Server Pages)前台页面:jsp由html代码和嵌入其中的java代码组成(比如EL表达式)。属于项目开发中的前台部分。jsp页面首先编译成servlet,再进一步编译。7、Java Servlet后台服务:Servlet全部由java语言写就,并生成html,属于服务端的应用。8、XML(Extensible Markup Language)配置:XML和java的发展是项目独立的。在java项目中,大量用到了xml配置环境变量。9、JMS(Java Message Service)消息服务,支持点对点,类似邮箱发送,支持发布/订阅,类似rss订阅。10、JTA(java Transaction Architecture)java事务api,被用于与事务服务进行通信,为启动事务,连接现有事务,提交事务和撤销事务提供标准的java api。11、JTS(Java TransactionService)12、JavaMail邮件服务、支持SMTP以及IMAP13、JAF(JavaBeans Activation Framework)以上是仅有的一点对于十三个规范的理解。十三个规范更加专业的解释: http://p.primeton.com/articles/55422442be20aa0bf60000c0
Provider Pattern 的介绍与 JavaScript 实现
hello 大家好,我是 superZidan,这篇文章想跟大家聊聊 设计模式-提供者模式
1. Provider Pattern 是什么?
Provider Pattern 是一种软件设计模式,它用于将数据或服务的提供与使用分离。该模式的核心思想是,通过引入一个提供者(Provider)来负责数据或服务的创建和管理,而使用者(Consumer)只需关注使用这些数据或服务即可。这种分离可以提高代码的模块化程度,降低耦合度,使系统更加灵活和可扩展。
Provider Pattern 的主要优点包括:
解耦性:通过将提供者和使用者分离,使得它们可以独立变化,而不会相互影响。可维护性:由于提供者和使用者的职责清晰,代码的维护和测试更加简单。可扩展性:可以通过替换提供者来改变数据或服务的实现,而不需要修改使用者的代码。代码复用:多个使用者可以共享同一个提供者,避免重复代码的编写。
2. JavaScript 实现 Provider Pattern
下面,我们将使用 JavaScript 来实现 Provider Pattern 的示例代码。在这个例子中,我们将创建一个简单的日志记录器,用于记录应用程序中的日志信息。我们将创建一个名为 LoggerProvider 的提供者,以及一个名为 LoggerConsumer 的使用者。
首先,让我们来定义 LoggerProvider 类:
class LoggerProvider {
constructor() {
this.logs = [];
log(message) {
this.logs.push(message);
getLogs() {
return this.logs;
在上述代码中,我们定义了一个 LoggerProvider 类,其中包含了 log 和 getLogs 方法。log 方法用于将日志信息存储在内部的 logs 数组中,getLogs 方法用于获取所有已记录的日志信息。
接下来,我们定义 LoggerConsumer 类:
class LoggerConsumer {
constructor(provider) {
this.provider = provider;
log(message) {
this.provider.log(message);
printLogs() {
const logs = this.provider.getLogs();
for (const log of logs) {
console.log(log);
在上述代码中,我们定义了一个 LoggerConsumer 类,它接受一个 provider 参数作为构造函数的参数。在 log 方法中,我们调用提供者的 log 方法将日志信息传递给提供者。在 printLogs 方法中,我们调用提供者的 getLogs 方法获取日志信息,并将其打印到控制台。
现在,我们可以使用这些类来创建提供者和使用者的实例,并进行测试:
const loggerProvider = new LoggerProvider();
const loggerConsumer = new LoggerConsumer(loggerProvider);
loggerConsumer.log('Log message 1');
loggerConsumer.log('Log message 2');
loggerConsumer.log('Log message 3');
loggerConsumer.printLogs();
运行上述代码,你将看到以下输出:
Log message 1
Log message 2
Log message 3
通过上述示例,我们可以看到 Provider Pattern 的应用。LoggerConsumer 类与具体的日志记录实现(LoggerProvider)解耦,使用者只需要关注日志记录的功能,而不需要关心具体的实现细节。这种分离使得代码更加灵活和可扩展,我们可以轻松地替换提供者的实现,或者为不同的使用者提供不同的实现。
3. Provider Pattern 的进一步应用
Provider Pattern 在实际的应用中可以有更多的变种和扩展。以下是一些常见的应用场景:
3.1. 数据库访问在应用程序中,我们经常需要与数据库进行交互。Provider Pattern 可以用于将数据库的访问和操作与应用程序的其他部分分离。我们可以创建一个数据库提供者,负责管理数据库连接、执行查询和更新等操作,而应用程序的其他部分则可以通过提供者来访问数据库。
3.2. 配置管理Provider Pattern 也可以用于配置管理。我们可以创建一个配置提供者,用于加载和管理应用程序的配置信息。使用者可以通过提供者来获取配置值,而不需要直接访问配置文件或环境变量。
3.3. 资源管理在一些情况下,我们需要管理和共享资源,如文件句柄、网络连接等。Provider Pattern 可以用于创建资源提供者,负责管理和分配这些资源,而使用者则可以通过提供者来获取和释放资源。
在本文中,我们介绍了 Provider Pattern 的概念和优点。通过将数据或服务的提供与使用解耦,Provider Pattern 可以提高代码的可维护性和可扩展性。我们还提供了使用 JavaScript 实现 Provider Pattern 的示例代码,并讨论了该模式在数据库访问、配置管理和资源管理等方面的进一步应用。
掌握 Provider Pattern 可以帮助编程新手提升编程能力,使代码更加模块化、可维护和可扩展。通过应用适当的设计模式,我们可以更好地组织和管理代码,提高软件开发的效率和质量。希望本文对你有所帮助,鼓励你在实际项目中尝试使用 Provider Pattern,并进一步探索其他设计模式的应用。
机房收费系统—— 一堆小问题1
开学两周了,机房才进行了那么一点点。惭愧。就先把这些小问题汇总一下。 首先配置ODBC,按照所给提示一步一步完成了。接下来安装程序。然后就遇到了一系列的小问题。1.with块未设置: 解决办法:把数据库的登陆密码改为123456。原来数据库模块中有数据库的用户名:sa,密码:123456,而我的是sa:112233。二、多步OLEDB错误: 解决办法: http://hl1871358.blog.163.com/blog/static/225526047201471384135263/三、控件注册不成功 明明已经注册成功了,还弹框儿了呢,怎么说控件注册不成功??原来是我的系统是64位的,与32位注册是不一样的目录,32位为C:\Windows\System32,而64位为C:\Windows\System。把控件放到64位系统目录下面,在运行中输入cmd,然后输入regsvr32+路径+名称.扩展名。四、 这些文本框不是每个文本框都可以输入东西的。在做学生的时候遇到这样的问题。combobox的类型选择了1。推测是源码出了问题,从.exe里面没法儿改,那就先这样。等我做这儿的时候就可以确定了。五、 Access中出现这个问题的解决办法: http://www.accessoft.com/article-show.asp?id=7449 字段类型定义错误导致数据类型错误。ADODB.Recordset与DAO.Recordset的区别。 另外一种方法: http://www.accessoft.com/article-show.asp?id=7606 需要注册ADO与DAO,方法在此: http://www.accessoft.com/article-show.asp?id=315 果然,我的电脑里C:\ProgramFiles\Common Files\Microsoft Shared目录下没有DAO文件夹。 但是,问题还是没有解决。 实在想不通是哪里出了问题。为此踌躇了两天,就想先创建数据库吧。于是发现了问题的根源所在。经思思提点,原来是数据库中对应的数据类型限制了输入字符的类型。只能输入数字,不能输入汉字或字母等。如此,修改数据库的数据类型,就把问题解决了,如果从用户的角度来解决的话,就是注意输入的字符只能是数字。这个对用户来说,相当郁闷。 如上是机房准备阶段出现的一些小问题,接下来是敲程序时的一些问题。咱们下篇见。
基于DDD实现的用户注册流程,很优雅!
欢迎回来,我是飘渺。今天继续更新DDD&微服务的系列文章。在前面的文章中,我们深入探讨了DDD的核心概念。我理解,对于初次接触这些概念的你来说,可能难以一次性完全记住。但别担心,学习DDD并不仅仅是理论的理解,更重要的是将这些理论应用到实践中,理解其设计原则和实施方法。就如同编程界的一句流行格言所说:“Don't talk, Show me the Code”。今天,我们将以实现用户注册流程为例,一步步展示如何在实践中应用DDD的设计思想和技术手段,这将有助于你更好地理解并记住DDD的核心概念。让我们一起开始吧!1. 实现领域层在DDD的四层架构中,领域层扮演着核心角色。因此,我们首先着手实现这一层,其模块包结构如下:1.1 配置依赖项<dependencies>
<dependency>
<groupId>com.jianzh5</groupId>
<artifactId>dailymart-common-spring-boot-starter</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>我们在领域层首先引入了一个通用工具包依赖,这个工具包提供了我们在后续开发中可能需要的一些通用功能。利用这个工具包,我们能够保持代码的整洁,避免在领域层重复编写一些基础功能代码。1.2 构造领域模型在第三篇《如何构建商城的领域模型》一文中,我们完成了用户领域对象的建模。其中,最关键的部分是聚合对象CustomerUser。@Data
@Builder
public class CustomerUser {
private Long customerId;
private String userName;
private CustomerUserPassword password;
private CustomerUserPhone phone;
private CustomerUserEmail email;
private Points points;
private DeliveryAddress defaultAddress;
private List<DeliveryAddress> deliveryAddresses;
private List<PointsRecord> pointsRecord;
}在实现用户注册流程时,我们注意到DailyMart系统对于用户注册活动有几个要求:用户注册时需要提供邮箱、手机号和用户密码,这样在登录时允许使用任何一种方式进行登录数据库不允许使用明文存储密码用户名的长度必须大于等于6为了满足注册功能的需求,我们对部分属性进行了进一步的抽象,将它们提升为DP(Domain Primitive)对象,这样能够保证它们内在的业务逻辑得到正确的封装。比如,我们将userName,password,email和phone都定义为了值对象,并为它们分别定义了合适的业务逻辑。1.3 介绍DP在我们的领域模型中,UserName,CustomerUserPassword,CustomerUserEmail和CustomerUserPhone都被设计为DP(Domain Primitive)。DP是一个拥有精准定义,自我验证和行为的值对象,它代表了业务领域的最小单元。在实际开发中,我们通常将一些具有业务含义和行为的属性抽象为DP,如此,我们就能够保证这些属性的业务逻辑得到正确的封装和执行。以CustomerUser对象来说,用户名、密码、邮箱、手机号它们有精准的定义(用户名长度必须>=6,密码必须进行加密,邮箱格式必须保证正确),能够自我验证(在构造函数或者工厂方法中验证自身的有效性),并且拥有特定的行为(例如密码的加密和比较)。1.4 构建资源库在DDD中,资源库(Repository)扮演着领域对象持久化的角色,它提供了一种方式,允许我们在不关注底层持久化细节的情况下,实现领域对象的查询和存储。在用户注册功能中,我们创建了CustomerUserRepository资源库接口,并定义了保存用户和按用户名、邮箱、电话查询用户数量的方法。public interface CustomerUserRepository {
CustomerUser save(CustomerUser customerUser);
Long countByUserNameOrEmailOrTelephone(String userName, String email, String phone);
}在用户注册流程中我们创建了接口CustomerUserRepository,同时提供了两个方法,分别用于保存领域对象和根据条件查询记录条数。2. 实现基础设施层接下来,我们将在DailyMart的基础设施层中实现数据持久化。在这里,我们将使用MyBatis-Plus,一款灵活且强大的 ORM 框架,来简化数据库操作。其模块的包结构如下:2.1 配置依赖项<dependencies>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
</dependency>
</dependencies>在这段依赖配置中,我们引入了mybatis-plus-boot-starter,这是 MyBatis-Plus 的启动器,用来支持与 Spring Boot 的集成。同时,mysql-connector-java是 MySQL 的 JDBC 驱动,负责连接和操作 MySQL 数据库。我们还引入了 mapstruct 和 mapstruct-processor,这是一个用于在 Java 对象之间进行映射转换的工具库,我们将用它来实现领域模型和数据模型的转换。2.2 构建数据模型@Data
@TableName("customer_user")
public class CustomerUserDO {
private Long customerId;
private String userName;
private String password;
private String email;
private String phone;
private int points;
}在这里,我们定义了 CustomerUserDO 类,用于映射数据库的 customer_user 表。它的每个属性都对应数据库表中的一个字段。2.3 实现模型转换器@Mapper(componentModel = "spring")
public interface CustomerUserConverter {
@Mappings({
@Mapping(target ="points",source = "customerUser.points.value"),
@Mapping(target = "password",source = "customerUser.password.password"),
@Mapping(target = "phone",source = "customerUser.phone.phone"),
@Mapping(target = "email",source = "customerUser.email.email")
CustomerUserDO domainToDO(CustomerUser customerUser);
}然后,我们使用 MapStruct 工具库定义了一个转换器接口 CustomerUserConverter,用来实现领域模型 CustomerUser 和数据模型 CustomerUserDO 之间的转换。2.4 构建数据访问对象public interface CustomerUserMapper extends BaseMapper<CustomerUserDO> {
}我们定义了 CustomerUserMapper 接口,继承自 MyBatis-Plus 的 BaseMapper 接口。这样,我们就可以使用 BaseMapper 提供的各种方法来进行数据库操作,大大简化了数据库访问的复杂性。2.5 实现仓储方法@Repository
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
@Slf4j
public class CustomerUserRepositoryImpl implements CustomerUserRepository {
private final CustomerMapper customerMapper;
private final CustomerUserConverter customerUserConverter;
@Override
public CustomerUser save(CustomerUser customerUser) {
CustomerUserDO customerUserDO = customerUserConverter.domainToDO(customerUser);
int insert = customerMapper.insert(customerUserDO);
if(insert < 1){
throw new RuntimeException("用户插入异常");
Long customerId = customerUserDO.getCustomerId();
customerUser.setCustomerId(customerId);
return customerUser;
@Override
public Long countByUserNameOrEmailOrTelephone(String userName, String email, String phone) {
QueryWrapper<CustomerUserDO> queryWrapper = new QueryWrapper<>();
queryWrapper.or().eq("user_name",userName)
.or().eq("email",email)
.or().eq("phone",phone);
return customerMapper.selectCount(queryWrapper);
}最后,我们实现了 CustomerUserRepository 接口,这是我们在领域层中定义的用户仓储接口。在实现类 CustomerUserRepositoryImpl 中,我们首先将领域模型转换为数据模型,然后通过 CustomerUserMapper 进行数据库操作。这样,领域模型和数据模型就实现了解耦,领域层和基础设施层之间的交互也变得更加灵活和便捷。3. 实现应用服务层现在我们将转向应用服务层的实现。其模块包结构如下:3.1 配置依赖项<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
</dependencies>在应用服务层,我们引入了 spring-boot-starter-validation 来对输入参数进行校验。这将保证我们的应用在接收到不符合要求的数据时能够响应适当的错误信息。3.2 构建数据传输对象(DTO)@Data
@Valid
public class UserRegistrationDTO {
@NotBlank(message = "用户名不能为空")
private String userName;
@NotBlank(message = "密码不能为空")
private String password;
@Email(message = "请输入正确的邮箱格式")
private String email;
@NotBlank(message = "手机号不能为空")
private String phone;
}我们定义了 UserRegistrationDTO 类,这是一个数据传输对象 (DTO),主要用作接口层和应用层之间传递数据。在这里,它包含了用户注册所需的所有数据,如用户名、密码、电子邮件和手机号。3.3 构建模型转换器@Mapper(componentModel = "spring")
public interface CustomerUserAssembler {
@Mappings({
@Mapping(target ="password",ignore = true),
@Mapping(target ="phone",source = "customerUser.phone.phone"),
@Mapping(target ="email",source = "customerUser.email.email")
UserRegistrationDTO domainToDTO(CustomerUser customerUser);
}我们使用了 MapStruct 工具库定义了 CustomerUserAssembler 接口,这是一个转换器,负责将领域模型 CustomerUser 转换为数据传输对象 UserRegistrationDTO。3.4 实现应用服务@Service
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
@Slf4j
public class CustomerUserService {
private final CustomerUserRepository customerUserRepository;
private final CustomerUserAssembler customerUserAssembler;
public UserRegistrationDTO register(UserRegistrationDTO userRegistrationDTO) {
// 1. 校验用户是否存在
boolean exists = existsByUserNameOrEmailOrTelephone(userRegistrationDTO.getUserName(), userRegistrationDTO.getEmail(), userRegistrationDTO.getPhone());
if(exists){
throw new RuntimeException("User already exists");
CustomerUser customerUser = CustomerUser.builder()
.userName(new CustomerUserName(userRegistrationDTO.getUserName()))
.phone(new CustomerUserPhone(userRegistrationDTO.getPhone()))
.email(new CustomerUserEmail(userRegistrationDTO.getEmail()))
.password(new CustomerUserPassword(userRegistrationDTO.getPassword()))
.build();
CustomerUser registerUser = customerUserRepository.save(customerUser);
return customerUserAssembler.domainToDTO(registerUser);
public boolean existsByUserNameOrEmailOrTelephone(String userName, String email, String phone) {
Long count = customerUserRepository.countByUserNameOrEmailOrTelephone(userName,email,phone);
log.info("记录条数{}",count);
return count >= 1;
}在 CustomerUserService 类中,我们实现了用户注册的应用服务。首先,我们检查用户是否已经存在;如果不存在,我们将创建一个新的 CustomerUser 并将其保存到仓库。然后,我们将新创建的 CustomerUser 转换为 UserRegistrationDTO,并返回给调用者。在领域驱动设计 (DDD) 中,我们经常将业务逻辑封装在领域模型中。然而,有些业务逻辑并不适合放在实体或值对象中,如这里的用户名唯一性检查,因为这需要与用户仓库进行交互,这是一个涉及基础设施的操作。领域模型应尽可能地与基础设施保持解耦,所以这样的业务逻辑更适合放在服务层中。4. 实现用户接口层最后,我们来实现用户接口层,作为与外部交互的主要入口。其模块包结构如下:4.1 配置依赖为了使用应用服务层的功能,我们需要添加其依赖项:<dependencies>
<dependency>
<groupId>com.jianzh5</groupId>
<artifactId>dailymart-customer-application</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>4.1 构建注册接口接下来,我们构建用户注册的RESTful接口。该接口将接收一个UserRegistrationDTO对象作为参数,并调用服务层的register方法进行用户注册。@RestController
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class CustomerController {
private final CustomerUserService customerService;
@PostMapping("/api/customer/register")
public UserRegistrationDTO register(@RequestBody @Valid UserRegistrationDTO customerDTO){
return customerService.register(customerDTO);
}4.2 配置启动类最后,我们需要配置应用的启动类,它将启动整个Spring Boot应用并扫描指定包中的Mapper接口。@SpringBootApplication
@MapperScan("com.jianzh5.dailymart.module.customer.infrastructure.dao.mapper")
public class CustomerUserApplication {
public static void main(String[] args) {
SpringApplication.run(CustomerUserApplication.class,args);
}4.3 测试验证完成了上述工作,就可以进行测试验证了。下面我用postman调用注册接口,用户可以成功注册,密码也被加密。当使用相同的用户名、手机号、邮箱注册时,后台日志会提示用户已存在的异常。6. 小结本篇文章中,我们详细地实现了用户注册功能在DDD架构下的设计和实现过程。首先,我们构建了精确的领域模型,然后建立基础设施层,实现数据的持久化。接着,我们通过应用服务层处理用户注册的请求与响应,编排领域模型的行为。最后,构建了用户接口层,处理HTTP请求。值得注意的是,本次实践中我们并没有采用领域服务,而是直接在应用服务层处理业务逻辑。这主要是因为注册功能的业务逻辑主要与基础设施层的交互有关,并未涉及到多个领域模型的协作。但在更复杂的业务场景中,我们可能会考虑引入领域服务。总体来说,这篇教程旨在帮助你更深入地理解DDD,并将其应用到实际的项目中。未来,我们将继续优化代码,并讨论如何统一接口层的返回值、处理异常等问题。系列文章,欢迎持续关注。
Mybatis mapper-locations作用
前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站 点击跳转浏览。mapper-locations顾名思义是一个定义mapper位置的属性在yml或properties下配置,作用是实现mapper接口配置见mapper和接口的绑定。使用场景:当mapper接口和mapper接口对应的配置文件在命名上相同所在的路径相同则mapper-locations可以不用配置,配置也不会生效。但是,如果当mapper接口和mapper接口对应的配置文件在命名上不同或所在的路径不同之一不同,需要配置mapper-locations才能实现接口的绑定实现接口绑定需要在配置文件中配置:mybatis.mapper-locations=classpath:mapper/*Mapper.xml
MyBatis详解
什么是MyBatis mybatis 是一个优秀的基于 java 的持久层框架,它内部封装了 jdbc,使开发者只需要关注 sql 语句本身,而不需要花费精力去处理加载驱动、创建连接、创建 statement 等繁杂的过程。 mybatis 通过 xml 或注解的方式将要执行的各种 statement 配置起来,并通过 java 对象和 statement 中sql 的动态参数进行映射生成最终执行的 sql 语句,最后由 mybatis 框架执行 sql 并将结果映射为 java 对象并返回。 采用 ORM 思想解决了实体和数据库映射的问题,对 jdbc 进行了封装,屏蔽了 jdbc api 底层访问细节,使我们不用与 jdbc api 打交道,就可以完成对数据库的持久化操作。 为了我们能够更好掌握框架运行的内部过程,并且有更好的体验,下面我们将从自定义 Mybatis 框架开始来学习框架。此时我们将会体验框架从无到有的过程体验,也能够很好的综合前面阶段所学的基础之前的jdbc 问题分析1、 数据库链接创建、释放频繁造成系统资源浪费从而影响系统性能,如果使用数据库链接池可解决此问题。2、 Sql 语句在代码中硬编码,造成代码不易维护,实际应用 sql 变化的可能较大, sql 变动需要改变 java代码。3、 使用 preparedStatement 向占有位符号传参数存在硬编码,因为 sql 语句的 where 条件不一定,可能多也可能少,修改 sql 还要修改代码,系统不易维护。4、 对结果集解析存在硬编码(查询列名), sql 变化导致解析代码变化,系统不易维护,如果能将数据库记录封装成 pojo 对象解析比较方便。利用MyBatis来进行添加 Mybatis3.4.5 的坐标<dependencies>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.5</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.10</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.12</version>
</dependency>
</dependencies>编写 User 实体类public class User implements Serializable {
private Integer id;
private String username;
private Date birthday;
private String sex;
private String address;
public Integer getId() {
return id;
public void setId(Integer id) {
this.id = id;
public String getUsername() {
return username;
public void setUsername(String username) {
this.username = username;
public Date getBirthday() {
return birthday;
public void setBirthday(Date birthday) {
this.birthday = birthday;
public String getSex() {
return sex;
public void setSex(String sex) {
this.sex = sex;
public String getAddress() {
return address;
public void setAddress(String address) {
this.address = address;
@Override
public String toString() {
return "User [id=" + id + ", username=" + username + ", birthday=" + birthday
+ ", sex=" + sex + ", address="
+ address + "]";
}编写持久层接口 IUserDaoIUserDao 接口就是我们的持久层接口(也可以写成 UserDao 或者 UserMapper) ,具体代码如下:
public interface IUserDao {
* 查询所有用户
* @return
List<User> findAll();
}编写持久层接口的映射文件 IUserDao.xml要求:创建位置: 必须和持久层接口在相同的包中。名称: 必须以持久层接口名称命名文件名,扩展名是.xml<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.itheima.dao.IUserDao">
<!-- 配置查询所有操作 -->
<select id="findAll" resultType="com.itheima.domain.User">
select * from user
</select>
</mapper>编写 SqlMapConfig.xml 配置文件<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!-- 配置 mybatis 的环境 -->
<environments default="mysql">
<!-- 配置 mysql 的环境 -->
<environment id="mysql">
<!-- 配置事务的类型 -->
<transactionManager type="JDBC"></transactionManager>
<!-- 配置连接数据库的信息:用的是数据源(连接池) -->
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/ee50"/>
<property name="username" value="root"/>
<property name="password" value="1234"/>
</dataSource>
</environment>
</environments>
<!-- 告知 mybatis 映射配置的位置 -->
<mappers>
<mapper resource="com/itheima/dao/IUserDao.xml"/>
</mappers>
</configuration>编写测试类public class MybatisTest {
public static void main(String[] args)throws Exception {
//1.读取配置文件
InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml");
//2.创建 SqlSessionFactory 的构建者对象
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
//3.使用构建者创建工厂对象 SqlSessionFactory
SqlSessionFactory factory = builder.build(in);
//4.使用 SqlSessionFactory 生产 SqlSession 对象
SqlSession session = factory.openSession();
//5.使用 SqlSession 创建 dao 接口的代理对象
IUserDao userDao = session.getMapper(IUserDao.class);
//6.使用代理对象执行查询所有方法
List<User> users = userDao.findAll();
for(User user : users) {
System.out.println(user);
//7.释放资源
session.close();
in.close();
}总结 通过快速入门示例,我们发现使用 mybatis 是非常容易的一件事情,因为只需要编写 Dao 接口并且按照mybatis 要求编写两个配置文件,就可以实现功能。远比我们之前的 jdbc 方便多了。(我们使用注解之后,将变得更为简单,只需要编写一个 mybatis 配置文件就够了。)
Spring中使用工厂模式解耦详解
前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站 点击跳转浏览。解耦 降低程序之间的耦合性为什么要降低程序之间的耦合性**原因很简单。当耦合度很高时,在代码维护过程中修改一个地方会涉及到很多地方。如果耦合关系不是澄清修改期间,后果可能是灾难性的,特别是对于有许多变化的项目需求和多人协作开发和维护,修改一个地方会导致模块的错误一直运行稳定,如果是严重的,将导致一个恶性循环,问题永远不能完全解决,开发和测试正与各种各样的问题作斗争。最终会导致项目延迟,降低用户满意度,增加成本。而且也提高了程序的复用性引入类之间的耦合性在我们学习jdbc访问数据库时,具体流程是首先在maven里面导入依赖<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
</dependency>
</dependencies>然后就是jdbc的实现过程:1.注册驱动2.获取连接3.获取操作数据库的预处理对象4.执行SQL,得到结果集5.遍历结果集6.释放资源具体代码如下:public class Jdbc {
public static void main(String[] args) throws Exception{
//1.注册驱动
// DriverManager.registerDriver(new com.mysql.jdbc.Driver());
Class.forName("com.mysql.jdbc.Driver");
//2.获取连接
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test","root","1234");
//3.获取操作数据库的预处理对象
PreparedStatement pstm = conn.prepareStatement("select * from account");
//4.执行SQL,得到结果集
ResultSet rs = pstm.executeQuery();
//5.遍历结果集
while(rs.next()){
System.out.println(rs.getString("name"));
//6.释放资源
rs.close();
pstm.close();
conn.close();
}第一种创建驱动方式进行分析,上面代码中被注释的那句代码DriverManager.registerDriver(new com.mysql.jdbc.Driver());如果把pom里面的依赖注释就相当于没有jar包,然后我们进行编译则不能编译,会是一个错误,如图所示:第二种创建驱动方式Class.forName(“com.mysql.jdbc.Driver”);这句代码就只依赖于一个字符串而不是一个驱动类。这样子当jar包不存在时,就是一个异常而不是一个错误。这样子就是避免的类之间的耦合性但是产生一个新的问题,导致这个字符串在类中写死了如果换成其他数据库的话,就还要改代码,所以我们要通过配置文件来获取要创建的对象全限定类名,然后再用反射来创建对象所以 解耦的思路:第一步:使用反射来创建对象,而避免使用new关键字。第二步:通过读取配置文件来获取要创建的对象全限定类名实际开发中:应该做到:编译期不依赖,运行时才依赖。业务层Service调用持久层Dao时的耦合性Dao层userdao层的接口public interface IUserDao {
void save();
}userdao层的实现类package Dao.IMPL;
import Dao.IUserDao;
public class UserDao implements IUserDao {
public void save() {
System.out.println("hello");
}UserService的接口package Service;
public interface IUserService {
void save();
}UserService的实现类package Service.IMPL;
import Dao.IMPL.UserDao;
import Service.IUserService;
public class UserService implements IUserService {
//private将这个对象私有化
//因为dao是一个接口的实现类 接口是不是实例化的 但是可以创建一个接口的实现类对象
private IUserDaodao=new UserDao();
public void save() {
System.out.println("hello");
}private IUserDaodao=new UserDao(); 这句代码就是Service层调用Dao层,当我们把UserDao的代码去除之后,程序就出现报错,不能编译成功,说明程序之间的耦合性太强了。工厂模式解耦合上面的service层调用dao层的耦合性太强之后我们可以参考之前的jdbc里面的步骤。第一步:使用反射来创建对象,而避免使用new关键字。
第二步:通过读取配置文件来获取要创建的对象全限定类名所以我们可以通过工厂来进行一个解耦BeanFactory:个创建Bean对象的工厂什么是Bean在计算机英语中,有可重用组件的含义,可重用的意思是一个servlet可能有多个service 一个service中可能有多个dao.一个servicc或者一个dao都是一个Bean第一步:需要一个配置文件来配置我们的service和dao 配置的内容:唯一标识=全限定类名(key=value)我们创建一个Bean.properties也可以是一个Bean.xml,在这里我们在resources里面创建一个Bean.properties记录里面的全限定类名UserService=Service.IMPL.UserService
UserDao=Dao.IMPL.UserDao第二步通过读取配置文件中配置的内容,反射创建对象package factory;
import java.io.InputStream;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
public class BeanFactory {
//读取配置文件
//定义一个静态Properties对象
private static Properties props;
// //定义一个Map,用于存放我们要创建的对象。我们把它称之为容器
//多例变单例
private static Map<String,Object> beans;
//使用静态代码块为Properties对象赋值
static {
try {
//实例化对象
props = new Properties();
//获取properties文件的流对象
//这个时候不能用new FileInputStream因为这这个时候的相对路肩和绝对路径都不管用
//we工程部署src没有了 相对路径不能用 绝对路径也不行
//要用类加载器来进行操作
InputStream in = BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties");
//加载一个流对象
props.load(in);
// //实例化容器
beans = new HashMap<String,Object>();
// //取出配置文件中所有的Key
Enumeration keys = props.keys();
// //遍历枚举
while (keys.hasMoreElements()){
// //取出每个Key
String key = keys.nextElement().toString();
// //根据key获取value
String beanPath = props.getProperty(key);
// //反射创建对象
Object value = Class.forName(beanPath).newInstance();
// //把key和value存入容器中
beans.put(key,value);
}catch(Exception e){
//抛出一个初始化错误 后面直接执行不了
throw new ExceptionInInitializerError("初始化properties失败!");
* 根据bean的名称获取对象 用Object类型做返回值
* @param beanName
* @return
public static Object getBean(String beanName){
return beans.get(beanName);
这样之后,我们再调用dao层时就可以把在BeanFactory里面创建private IUserDao dao= (IUserDao) BeanFactory.getBean("UserDao");同样调用service代码也发生变化IUserService service= (IUserService) BeanFactory.getBean("UserService");这样之后,再把删除就只报异常,而且如果需要更改代码里面的值,我们就只用更改配置文件里面的代码这样就可以更加快速的进行更改操作。新创建一个公众号 Rockey小何同学 想相互交流的同学可以关注一下哈! 感谢支持!