技术资料查询
一起进步

Repository模式【转】

shanhubei阅读(282)

最近开发的MVC项目使用了Repository模式。

啥是Repository模式?

从图看,有一个仓库接口,一个实现了这个仓库接口的基类;然后在使用方,一方面,要声明一个继承于仓库接口的子接口,另一方面,编写一个数据库操作类,继承仓库基类,并实现这个子接口。

继承仓库基类容易理解,为啥还要搞一个子接口呢?直接实现仓库接口不就完啦?思考其中原因,应该是为了控制反转,依赖注入,总之一个类对应一个接口就是了。

Repository模式意义何在呢?

Repository模式是一个中间层,位于 数据库映射层 和 领域层(业务逻辑层)之间。本来嘛,ORM或者DAL已经为我们隔离了数据库,BLL并没有直接访问数据库,如果数据库更换,那改写ORM或DAL即可。那么现在又增加一层Repository,目的何在呢?

摘录一些话,姑妄听之。反正他们牛逼,怎么说都行:

Repository 是一个独立的层,介于领域层与数据映射层 (数据访问层) 之间。它的存在让领域层感觉不到数据访问层的存在,它提供一个类似集合的接口提供给领域层进行领域对象的访问。Repository 是仓库管理员,领域层需要什么东西只需告诉仓库管理员,由仓库管理员把东西拿给它,并不需要知道东西实际放在哪。(咦,难道DAL\ORM不是这样的吗?)
Repository 模式是架构模式,在设计架构时,才有参考价值;
Repository 模式主要是封装数据查询和存储逻辑;
Repository 模式实际用途:更换、升级 ORM 引擎,不影响业务逻辑;
Repository 模式能提高测试效率,单元测试时,用 Mock 对象代替实际的数据库存取,可以成倍地提高测试用例运行速度。

这几句话中,好像亮点在于换ORM,比如将NHibernate换成EF,BLL也不受影响。呵呵。

也有一些钻研者自我安慰:
使用Repository,隐含着一种意图倾向,就是 Domain需要什么我才提供什么,不该提供的功能就不要提供,一切都是以Domain的需求为核心;而使用Dal,其意图倾向在于我Dal层能使用的数 据库访问操作提供给Business层,你Business要用哪个自己选。换一个Business也可以用我这个Dal,一切是以我Dal能提供什么操 作为核心。

也有后起之秀顿悟:
仓储模式最大的优点就是所有的数据访问首先是通过仓库的,对仓库的增删改都不会立即提交到数据库,而只有当调用了仓库包裹器,这些增删改的操作才会一次提交到数据库。

但我怎么看,都看不出这个批量提交、仓库包裹器与Repository有什么关系。

今天看到一种隐隐约约的说法,说资源库(大概就是Repository模式吧)有个好处,就是可以兼容缓存。BLL通过资源库来存取数据,而不必知道这些数据是来自于数据库还是缓存。
————————————————
版权声明:本文为CSDN博主「架构师夏老师」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/ZuoAnYinXiang/article/details/80711936

Dao和Repository,你还傻傻分不清吗?

shanhubei阅读(173)

DAO到底有没有必要?

        贫血模型中的DAO或领域模型中的Repository到底有没有必要?有人认为DAO或者说Repository是充血模型的大敌,对此我无论如何也不赞同。DAO或Repository是负责持久化逻辑的,如果取消掉DAO或Repository,将持久化逻辑直接写入到model对象中,势必造成model对象承担不必要的职责。虽然现在的ORM框架已经做得很好了,持久化逻辑还是需要大量的代码,持久化逻辑的掺入会使model中的业务逻辑变得模糊。允许去掉DAO的一个必要条件就是Java的的持久化框架必须足够先进,持久化逻辑的引入不会干扰业务逻辑,我认为这在很长一段时间内将无法做到。在rails中能够将DAO去掉的原因就是rail中实现持久化逻辑的代码很简洁直观,这也与ruby的表达能力强有关系。DAO的另外一个好处隔离数据库,这可以支持多个数据库,甚至可以支持文件存储。基于DAO的这些优点,我认为,即使将来Java的持久化框架做得足够优秀,使用DAO将持久化逻辑从业务逻辑中分离开来还是十分必要的,况且它们本身就应该分离。

        争论的焦点到了DAO上,确实,实践中很多项目最终实施DDD的结果就是把所有的DAO重命名为Repository。但是我认为DAO和Repository很像,但是不是一个东西,因为它们出发点不同。

为什么要有DAO?
        因为之前,很早之前,我们对于框架中立性还很受用。DAO给了我们可以随时把Hibernate换成ibatis的幻觉,所以我们要有一个地方隔离了框架。
        而且DAO集中了所有的查询,方便了性能调优人员。同时也鼓励了查询的重用,同样方便了调优。

为什么要有Repository?
        在我看来,与其说Publication Repository,不如说Publications。Repository是一个集合对象,它封装了集合的逻辑。因为它具有封装性,所以它应该负责保持这个集合的状态,比如拒绝一些非法的的修改。Repository的入口是聚合根。

Java代码

class PublicationRepository {  
  public void save(Publication pub) {  
   if (hasSameName(pub)) {  
     throw new InvalidPublicationException();  
   }  
   dao.save(pub);  
  }  
}  

        另外,Repository只应该负责Aggregate Root。对于被Aggregate的对象,应该用Navigation,也就是在关系之间游走来获取。所以不是所有的查询都必须由Repository来完成,比如说:

Java代码

class Contact {  
  private List<ContactNote> contactNotes = new ArrayList<ContactNote>();  
  public void contactedBy(User accountManager, DateTime time){  
    ContactNote contactNote = new ContactNote(this, accountManager, time);  
    if (isDuplicated(contactNote)) {  
      throw new InvalidContactNote();  
    }  
    contactNotes.add(contactNote);  
  }  
  private boolean isDuplicated(ContactNote contactNote) {  
    // 查询contactNotes  
    return xxx;  
  }  
}  

        现状是,对象之间的关联不可查询导致了,很多这样的查询必须通过xxxDao,xxxRepository来完成。其实它们都不应该插手。

        理想情况下,只有业务开始的时候用repository加载对象,在结束的时候用repository把对象存储回去,中间都是领域对象在互相作用。而DAO,可以以Generic Query Builder的形式存在,不过和它之前被发明出来的意图已经不是一个东西了。

        DAO原本的作用就是隔离数据库的影响,没有业务逻辑。而Repository更抽象,从概念上来说是一个可以全局访问的集合,从这个意义上来讲对你所举Publication Repository,使用add(Publication pub)作为方法签名要更好一些。Repository也负责保持完整对象的完整性,PublicationRepository的例子也说明了这一点,另外一个例子,但从数据库重建一个对象时,由于外部原因,对象已经变得不完整,将它恢复为一个完整的对象或者直接抛异常也是Repostory的责任,它可以将这种保证对象完整性的责任委托给别的对象(如Factory)。
————————————————
版权声明:本文为CSDN博主「liuhongtaowxp1」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/liuhongtaowxp1/article/details/123983419

java框架中的controller层、dao层、domain层、service层、view层【转】

shanhubei阅读(145)

1.Controller层:接口层,用户访问请求时对接。

    Controller层负责具体的业务模块流程的控制,在此层里面要调用Serice层的接口来控制业务流程,控制的配置也同样是在Spring的配置文件里面进行,针对具体的业务流程,会有不同的控制器,我们具体的设计过程中可以将流程进行抽象归纳,设计出可以重复利用的子单元流程模块,这样不仅使程序结构变得清晰,也大大减少了代码量。

2.dao层:DAO层主要是做数据持久层的工作,负责与数据库进行联络的一些任务都封装在此,

     DAO层的设计首先是设计DAO的接口,然后在Spring的配置文件中定义此接口的实现类,然后就可在模块中调用此接口来进行数据业务的处理,而不用关心此接口的具体实现类是哪个类,显得结构非常清晰,DAO层的数据源配置,以及有关数据库连接的参数都在Spring的配置文件中进行配置。

3.domain层:通常就是用于放置这个系统中,与数据库中的表,一一对应起来的JavaBean的

    domain的概念,通常会分很多层,比如经典的三层架构,控制层、业务层、数据访问层(DAO),此外,还有一个层,就是domain层。

model层:和domain区别;可能都是javaBean,

这个区别是用途不同,domain通常就代表了与数据库表–一一对应的javaBean,

model通常代表了不与数据库一一对应的javaBean,但是封装的数据是前端的JS脚本,需要使用的数据

4.service层:Service层主要负责业务模块的逻辑应用设计。

     同样是首先设计接口,再设计其实现的类,接着再Spring的配置文件中配置其实现的关联。这样我们就可以在应用中调用Service接口来进行业务处理。Service层的业务实现,具体要调用到已定义的DAO层的接口,封装Service层的业务逻辑有利于通用的业务逻辑的独立性和重复利用性,程序显得非常简洁。

5.view视图层:此层与控制层结合比较紧密,需要二者结合起来协同工发。View层主要负责前台jsp页面的表示。

问题一:Service层并没有做什么实际的工作,只是接受了Servlet,同时又调用了Dao。它本身并没有什么实际意义的代码,只是接收,调用。
很显然,这样增加了代码量。当然,我很轻易感受到了。但是它的优点是什么呢?不可能没有优点的情况下仅仅是为了增加代码量吧?


问题的思考
关于 Service 层存在的意义
1.Service被称作业务层。顾名思义,它处理逻辑上的业务,而不去考虑具体的实现。
2.对于MVC模式,MVC本身并不属于设计模式的一种,它是一种设计结构,这种结构的最终目的是为了解耦,也就是不同逻辑层的代码自身改变的时候,你别影响其他层。在写项目的时候,不同的逻辑上的代码之间的解耦是很重要的。
那很显然,为了使得我们在写代码的时候,不同的逻辑层内的代码之间的关联降低到最小,我们需要在不同的逻辑层之间加一些缓冲的层来达到一些解耦的效果。
3.比如,你在视图层,不会直接去调用Dao层。
那么对于Service,就是 Servlet 和 Dao 层之间缓冲的层。通过这一层来进行解耦,使得 Dao 层内的变化不会直接影响到 Servlet 层。
例如一个 sql 语句如果需要拼接,比如说是模糊查询, 则 sql 语句需要根据用户选择的条件来进行拼接。那么,拼接这个 sql 语句的逻辑部分,就放在 service 层进行。而 Dao 层,只负责接收拼接之后的最终的 sql 语句。
       最后,service 也不是就非他不可。对于极小的项目而言,加了service层,反而增加了代码量,而且Dao层种以及预见了可能出现的情况,并进行了相应的扩展。那么,此时,既不需要了。当然,大型项目可能无法在Dao层内做到这些(我也没接触到过大型项目),就需要service了。
最终的目的也就两个词:解耦,便于扩展

本文作者:TwcatL_tree

本文链接:https://www.cnblogs.com/twcat/p/16912664.html

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

@EqualsAndHashCode(callSuper = true/false) 作用

shanhubei阅读(1216)

首先 @EqualsAndHashCode 标在子类上

1. callSuper = true,根据子类自身的字段值和从父类继承的字段值 来生成hashcode,当两个子类对象比较时,只有子类对象的本身的字段值和继承父类的字段值都相同,equals方法的返回值是true。

2. callSuper = false,根据子类自身的字段值 来生成hashcode, 当两个子类对象比较时,只有子类对象的本身的字段值相同,父类字段值可以不同,equals方法的返回值是true。

    package com.jt.test;
     
    import lombok.AllArgsConstructor;
    import lombok.Data;
    import lombok.EqualsAndHashCode;
    import lombok.NoArgsConstructor;
     
    /**
     * @Author: ldj
     * @Date: 2022/03/29/15:39
     * @Description:
     */
    public class HashCodeTest {
        public static void main(String[] args) {
            Cat cat1 = new Cat("黑色", "鲁鲁", 2);
            Cat cat2 = new Cat("白色", "鲁鲁", 2);
     
            System.out.println(cat1.equals(cat2)); //true  callSuper = false
            System.out.println(cat1.equals(cat2)); //false callSuper = true
        }
    }
     
    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    class Animal {
        private String color;
    }
     
    @Data
    @EqualsAndHashCode(callSuper = false) //排除父类字段
    class Cat extends Animal {
        private String name;
        private Integer age;
     
        public Cat(String color, String name, Integer age) {
            super(color);
            this.name = name;
            this.age = age;
        }
    }

后续:Lombok 针对特定字段自定义get/set方法 ,排除@Data的覆盖问题

package com.atguli.common.demo;
     
    import lombok.AccessLevel;
    import lombok.AllArgsConstructor;
    import lombok.Data;
    import lombok.Getter;
     
    /**
     * User: ldj
     * Date: 2022/9/30
     * Time: 21:47
     * Description:
     * 结论:
     * @Getter(AccessLevel.NONE) User1不会生成getAge()方法
     * 即便没有@Getter(AccessLevel.NONE),但手动重写了getAge,
     * 以重写后的为准
     */
    public class GetterDemo {
        public static void main(String[] args) {
            User1 ldj = new User1("ldj", 18);
            System.out.println(ldj);
        }
    }
     
    @Data
    @AllArgsConstructor
    class User1 {
        private String name;
     
        @Getter(AccessLevel.NONE)
        private Integer age;
     
        public Integer getAge() {
            return this.age * 100;
        }
    }

java的long的小l和大L区别

shanhubei阅读(961)

首先几乎在所有位置,long的小写和大写都可以互相替换。其次L本质是对象,不是基础类型,具有Object的特性。

包装类把基本类型转换为对象,每个基本类型在java.lang包中都有一个相应的包装类

包装类型: Integer 、Long、Short、Byte、Character、Double、Float、Boolean、BigInteger、BigDecmail

基本类型和包装类的异同

在Java中,一切皆对象,但八大基本类型却不是对象。
声明方式的不同,基本类型无需通过new关键字来创建,而封装类型需new关键字。
存储方式及位置的不同,基本类型是直接存储变量的值保存在堆栈中能高效的存取,封装类型需要通过引用指向实例,具体的实例保存在堆中。
初始值的不同,封装类型的初始值为null,基本类型的的初始值视具体的类型而定,比如int类型的初始值为0,boolean类型为false;
使用方式的不同:如与集合类合作使用时只能使用包装类型。
long类型最好以大写L来添加尾缀,因为小写l容易和数字1混淆。 

Java @Data注解

shanhubei阅读(966)

1、@Data注解是lombok.jar包下的注解,该注解通常用在实体bean上,不需要写出set和get方法,但是具备实体bean所具备的方法,简化编程提高变成速度。

 2、@Data相当于@Getter @Setter @RequiredArgsConstructor @ToString @EqualsAndHashCode这5个注解的合集。
    使用@Data注解前

private String yhz;
     
public String getYhz() {
    return yhz;
}
     
public void setYhz(String yhz) {
   this.yhz = yhz;
}

    使用@Data注解后

@Data
public class Class {
     private String yhz;
}

eclipse导入项目时lombok包(@Data、@Slf4j…)注解无效

shanhubei阅读(1038)

1、找到maven仓库lombok的jar包位置,或者在lombok官网(https://www.projectlombok.org/download)下载lombok的jar包

2、打开cmd ,运行javaw -jar lombok.jar (lombok的jar包全称)

3、点击Specify location…

,找到你的eclipse软件所在的目录,选择你的eclipse的exe文件

 4、点击Install / Update即可安装lombok成功。重启eclipse并maven update一下项目。

@RestController

shanhubei阅读(971)

@RestController注解初步理解
一、在Spring中@RestController的作用等同于@Controller + @ResponseBody。

所以想要理解@RestController注解就要先了解@Controller和@ResponseBody注解。

二、@Controller注解

在一个类上添加@Controller注解,表明了这个类是一个控制器类。但想要让这个类成为一个处理请求的处理器光有@Controller注解是不够的,他还需要进一步修炼才能成为一个处理器。

1.在spring容器中创建该类的实例。创建实例的方式有两种:

1

  上述这种方式是在spring容器中注入单个bean,当项目比较大,控制器类比较多时,用这种方式向Spring容器中注入bean非常的让人苦恼,索性有第二种方式。

1

  这种方式会扫描指定包中的所有类,并生成相应的bean注入到spring容器中。使用这种方式当然能够极大提高我们的开发效率,但是有时候我们不想某一类型的类注入到spring容器中。

这个时候第二种方式也可以解决。 

<context:component-scan base-package=“test” >
  <context:include-filter type=“annotation” expression=“org.springframework.stereotype.Service”/>
</context:component-scan>


  上述代码表示扫描test包中除有@Service注解之外的类。

2.将@Controller注解的类注入Spring容器中,只是该类成为处理器的第一步,想要修炼大成,还需要在该类中添加注解@RequestMapping。

@RequestMapping注解是用来映射请求的,即指明处理器可以处理哪些URL请求,该注解既可以用在类上,也可以用在方法上。

当使用@RequestMapping标记控制器类时,方法的请求地址是相对类的请求地址而言的;当没有使用@RequestMapping标记类时,方法的请求地址是绝对路径。

@RequestMapping的地址可以是uri变量,并且通过@PathVariable注解获取作为方法的参数。也可以是通配符来筛选请求地址。具体的使用方法不是本次的重点,有兴趣的可以看

https://www.cnblogs.com/xiepeixing/p/4243288.html

 @Controller
  @RequestMapping("/user")
   public class UserController{

   @RequestMapping("/users")
   public String users() {
   return “users”;
   }

 }



  此时请求users方法的url路径就是:…/user/users。

可以看到上面users方法的返回值是字符串类型的,这个就是处理器在处理完任务后将要跳转的页面。如果想要方法直接返回结果,而不是跳转页面,这就要用到@ResponseBody注解了。

三、@ResponseBody注解

@ResponseBody表示方法的返回值直接以指定的格式写入Http response body中,而不是解析为跳转路径。

格式的转换是通过HttpMessageConverter中的方法实现的,因为它是一个接口,因此由其实现类完成转换。

如果要求方法返回的是json格式数据,而不是跳转页面,可以直接在类上标注@RestController,而不用在每个方法中标注@ResponseBody,简化了开发过程。

理解@RestController过程中参考了下面这些博客,非常感谢

https://www.cnblogs.com/daimajun/p/7152970.html

https://blog.csdn.net/l358366885/article/details/79485497

https://blog.csdn.net/sunroyfcb/article/details/81048675


Java保留2位小数(六种方法)

shanhubei阅读(1039)

一、使用 java.math.BigDecimal 类

public static String format1(double value) {
            BigDecimal bd = new BigDecimal(value);
            bd = bd.setScale(2, RoundingMode.HALF_UP);
            return bd.toString();
        }

二、使用 java.text.DecimalFormat 类

 public static String format3(double value)  {
            NumberFormat nf = NumberFormat.getNumberInstance();
            nf.setMaximumFractionDigits(2);
            /*
             * setMinimumFractionDigits设置成2
             * 如果不这么做,那么当value的值是100.00的时候返回100
             * 而不是100.00
             */
            nf.setMinimumFractionDigits(2);
            /*
             * 如果想输出的格式用逗号隔开,可以设置成true
             */
            nf.setGroupingUsed(false);
            return nf.format(value);
        }

三、使用 java.text.NumberFormat 类      

 public static String format3(double value)  {
            NumberFormat nf = NumberFormat.getNumberInstance();
            nf.setMaximumFractionDigits(2);
            /*
             * setMinimumFractionDigits设置成2
             * 如果不这么做,那么当value的值是100.00的时候返回100
             * 而不是100.00
             */
            nf.setMinimumFractionDigits(2);
            /*
             * 如果想输出的格式用逗号隔开,可以设置成true
             */
            nf.setGroupingUsed(false);
            return nf.format(value);
        }

四、使用 java.util.Formatter 类 

 public static String format4(double value) {
            /*
             * %.2f % 表示 小数点前任意位数 2 表示两位小数 格式后的结果为 f 表示浮点型
             */
            return new Formatter().format("%.2f", value).toString();
        }

五、使用String的format方法 

        public static String format6(double value) {
            long l =(long)(value*100) ;
            double d = l/100D;
     
            return String.valueOf(d);
        }

六、通过运算

        public static String format6(double value) {
            long l =(long)(value*100) ;
            double d = l/100D;
     
            return String.valueOf(d);
        }