SpringBoot之【studentManager-demo-service】简要设计【学生管理系统】【学生、班级、年级】【一对多、多对一】【非主键自增】【Jpa】

设计共分以下几步:

①依赖导入(这个应该看需要导入)

②实体类设计(通过自动建表,减少数据库设计)

③数据操作设计(使用JpaRepository,减少大量麻烦)

④series层和其实现的设计

⑤一些测试的设计


①依赖导入(Gradle):

如果是maven也很简单,此处就不多做介绍了。

可以看出这里是2.3.4.RELEASE版本的springboot。

plugins {
	id 'org.springframework.boot' version '2.3.4.RELEASE'
	id 'io.spring.dependency-management' version '1.0.10.RELEASE'
	id 'java'
}

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-actuator'
	implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
	implementation 'org.springframework.boot:spring-boot-starter-security'
	implementation 'org.springframework.boot:spring-boot-starter-web'
	compileOnly 'org.projectlombok:lombok'
	compile group: 'com.alibaba', name: 'druid', version: '0.2.9'
	developmentOnly 'org.springframework.boot:spring-boot-devtools'
	runtimeOnly 'mysql:mysql-connector-java'
	annotationProcessor 'org.projectlombok:lombok'
	testImplementation('org.springframework.boot:spring-boot-starter-test') {
		exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
	}
}

②实体类设计:

此部分一共有三个实体类:

Student、Clazz、Grade

注意:class容易出现误解、错误等问题,使用clazz代替班级class。

首先是Student实体类:

import lombok.*;
import org.hibernate.annotations.Type;
import javax.persistence.*;
import java.io.Serializable;
import java.time.LocalDate;
import java.util.UUID;

@Entity
@Table(name = "tb_student")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class Student implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "suuid")
    @Type(type = "uuid-char")
    private UUID suuid;

    @Column(columnDefinition = "int(10) not null UNIQUE auto_increment")
    private Integer snumber;

    @Column
    private String sgender;

    @Column
    private String sname;

    @Column
    private LocalDate birthday;

    @ManyToOne
    @JoinColumn(name = "cname", referencedColumnName = "cname")
    private Clazz clazz;

    @Override
    public String toString() {
        return "Student:[uuid = " + getSuuid() +
                " ,s_number = " + getSnumber() +
                " ,s_name = " + getSname() +
                " ,s_gender =" + getSgender() +
                " ,s_birthday = " + getBirthday() +
                " ,class_name = " + getClazz().getCname() +
                "]";
    }
}

然后是Clazz实体类:

import lombok.*;
import org.hibernate.annotations.Type;

import javax.persistence.*;
import java.io.Serializable;
import java.util.Set;
import java.util.UUID;

@Entity
@Table(name = "tb_class")
@AllArgsConstructor
@NoArgsConstructor
@Getter
@Setter
public class Clazz implements Serializable {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "cuuid")
    @Type(type = "uuid-char")
    private UUID cuuid;

    @Column
    private String cname;

    @ManyToOne
    @JoinColumn(name = "gName", referencedColumnName = "gName")
    private Grade grade;

    @Column
    @OneToMany(cascade = CascadeType.ALL, mappedBy = "clazz", fetch = FetchType.EAGER)
    private Set<Student> studentList;
    
    @Override
    public String toString() {
        return "Clazz:[uuid = " + getCuuid() +
                " ,c_name = " + getCname() +
                " ,Grade_name = " + getGrade().getGname() +
                "]";
    }
}

最后是Grade实体类:

import lombok.*;
import org.hibernate.annotations.Type;

import javax.persistence.*;
import java.io.Serializable;
import java.util.Set;
import java.util.UUID;

@Entity
@Table(name = "tb_grade")
@NoArgsConstructor
@AllArgsConstructor
@Getter
@Setter
@ToString
public class Grade implements Serializable {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "guuid")
    @Type(type = "uuid-char")
    private UUID guuid;

    @Column
    private String gname;

    @Column
    @OneToMany(cascade = CascadeType.ALL,mappedBy = "grade",fetch = FetchType.EAGER)
    private Set<Clazz> clazzList;
}

需要注意的是:我这里没有重写hashcode和equals,有需要的话记得重写;另外如果需要特殊getter、setter自己重写;我这里的Serializable接口在一开始的demo中并不需要,再后来添加了一些东西以后不添加就会报错。这个接口就类似于一个标签,告诉它我需要序列化或者反序列化。

还有一对多的时候我尝试将fetch改为lazy,但是会报错,另外如果是UUID类型保存(JpaRepository)进mysql(8.0.12)的时候会变成乱码,于是添加了@Type注解,还有就是想要一个int类型的自增键,当做学号,我用的是@Column(columnDefinition = "int(10) not null UNIQUE auto_increment")完成的,因为主键不是学号,是UUID,所以在主键是AUTO的时候,如果再去给学号添加@GeneratedValue(strategy = GenerationType.IDENTITY),虽然不会报错,但是并没有用,所以在经过尝试以后这种方式是可以实现非主键自增的。

【实体类属于dao层中的dbo,另外还有dto,不过在这个demo中没有写明,在另一个地方有,但是我嫌麻烦就懒得去找了,这里我贴上这个demo的git地址:https://github.com/Tezzxamo/demoStudent.git,有刚学的同学可以新建一个文件夹,然后在文件夹中打开gitbash,使用git命令clone下来,或者直接在idea中点击后在url中输入即可。】

 

【note:在postgres数据库中不能使用@Column(columnDefinition = "int(10) not null UNIQUE auto_increment")】




③数据操作设计:

每一个实体类对应一个repo:

import com.example.demo.dao.dbo.Student;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.Set;
import java.util.UUID;

public interface StudentRepo extends JpaRepository<Student, UUID> {

    Set<Student> findByClazzCname(String cname);

}
import com.example.demo.dao.dbo.Clazz;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.Optional;
import java.util.Set;
import java.util.UUID;

public interface ClazzRepo extends JpaRepository<Clazz, UUID> {
    Set<Clazz> findByGradeGname(String gname);

    Optional<Clazz> findByCname(String cname);

    Optional<Clazz> findByCnameAndGradeGname(String cname,String gname);
}
import com.example.demo.dao.dbo.Grade;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.Optional;
import java.util.UUID;

public interface GradeRepo extends JpaRepository<Grade, UUID> {
    Optional<Grade> findByGname(String gname);
}

注意:我在这里写到的都是我写demo时候用到的,如果你不需要你也可以删除,你也可以新建,而且不需要写实现方法,因为Jpa会帮你做到,但是前提是你写的方法名符合jpa的要求,一般来说idea会有提示(但也不排除有些不会有提示,那你就自求多福吧,我也不知道解决办法,如果你知道的话记得评论告诉我,我遇到过没有的,但我自己用的时候有),有提示的,jpa一般都会帮你做到(不排除个别情况),有了jpa,就可以少些很多实现、sql等。

但这并不意味着impl不存在了,impl是很重要的,另外repo也不能代替service,因为很多业务逻辑仅靠jpa也是实现不了的,所以这个时候service和service.impl就有了它们的作用。



④series层和其实现的设计:

我在刚写这个demo的时候,是每个实体类对应了一个service,但是最后我将它们合在了一起,但是合在一起的那个demo现在(写博客的时候)不在我的手上,也不在git上,所以就当做是留一个练习题,刚学习的同学,可以考虑如何将三个service合在一起并实现。

import com.example.demo.dao.dbo.Student;

import java.util.Set;

public interface StudentService {
    Set<Student> findByGradeGname(String gname) throws Exception;
}
import com.example.demo.dao.dbo.Clazz;

import java.util.Map;
import java.util.Set;

public interface ClazzService {
    String sexRatioOne(String cname) throws Exception;

    String sexRatioTwo(String cname) throws Exception;

    Boolean updateClazzWithGrade(String cname,String gname) throws Exception;

    Map<String, Set<Clazz>> classify();

    Boolean existByName(String cname);

    Boolean existByCnameAndGname(String cname,String gname);
}
import com.example.demo.dao.dbo.Clazz;

import java.util.Map;
import java.util.Set;

public interface GradeService {
    Map<String, Set<Clazz>> classify();
    
    Boolean existByName(String gname);
}

注意:我写的service中的方法不一定很好,有些甚至是冗余的,只不过一开始写的时候顺手就写上去了。

注意:import里面的内容要记得改,比如我的Student实体类的路径可能就和你不一样,除非你git上down的代码。

import com.example.demo.dao.dbo.Clazz;
import com.example.demo.dao.dbo.Student;
import com.example.demo.dao.repo.ClazzRepo;
import com.example.demo.dao.repo.GradeRepo;
import com.example.demo.dao.repo.StudentRepo;
import com.example.demo.service.GradeService;
import com.example.demo.service.StudentService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.stream.Collectors;

@Service
public class StudentServiceImpl implements StudentService {

    StudentRepo studentRepo;
    ClazzRepo clazzRepo;
    GradeRepo gradeRepo;
    GradeService gradeService;

    @Autowired
    public StudentServiceImpl(StudentRepo studentRepo,
                              ClazzRepo clazzRepo,
                              GradeRepo gradeRepo,
                              GradeService gradeService) {
        this.studentRepo = studentRepo;
        this.clazzRepo = clazzRepo;
        this.gradeRepo = gradeRepo;
        this.gradeService = gradeService;
    }

    @Override
    public Set<Student> findByGradeGname(String gname) throws Exception {
        // 校验
        if (!gradeService.existByName(gname)) {
            throw new Exception("没有该年级");
        }
        Set<Student> studentSet = new HashSet<>();
        //流的方式获取studentSet然后返回
        clazzRepo.findByGradeGname(gname)
                .stream()
                .map(Clazz::getStudentList)
                .collect(Collectors.toSet())
                .forEach(studentSet::addAll);
        return studentSet;
    }
}
import com.example.demo.dao.dbo.Clazz;
import com.example.demo.dao.dbo.Grade;
import com.example.demo.dao.dbo.Student;
import com.example.demo.dao.repo.ClazzRepo;
import com.example.demo.dao.repo.GradeRepo;
import com.example.demo.dao.repo.StudentRepo;
import com.example.demo.service.ClazzService;
import com.example.demo.service.GradeService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.*;
import java.util.stream.Collectors;

@Service
public class ClazzServiceImpl implements ClazzService {


    ClazzRepo clazzRepo;
    StudentRepo studentRepo;
    GradeRepo gradeRepo;
    GradeService gradeService;

    @Autowired
    public ClazzServiceImpl(ClazzRepo clazzRepo,
                            StudentRepo studentRepo,
                            GradeService gradeService,
                            GradeRepo gradeRepo) {
        this.clazzRepo = clazzRepo;
        this.studentRepo = studentRepo;
        this.gradeService = gradeService;
        this.gradeRepo = gradeRepo;
    }


    public Map<String, Long> sexRatio(String cname) throws Exception {
        Map<String, Long> map = new HashMap<>(10);
        //校验
        if (!existByName(cname)) {
            throw new Exception("不存在该班级");
        }
        Set<Student> studentSet = studentRepo.findByClazzCname(cname);
        long men = studentSet.stream()
                .filter(student -> "male".equals(student.getSgender()))
                .count();
        long women = studentSet.size() - men;
        map.put("men", men);
        map.put("women", women);
        return map;
    }

    @Override
    public String sexRatioOne(String cname) throws Exception {
        Map<String, Long> map = sexRatio(cname);
        long men = map.get("men");
        long women = map.get("women");
        String re;
        if (men >= women) {
            BigDecimal bg = new BigDecimal((double) men / women).setScale(1, RoundingMode.UP);
            re = bg + ":1";
        } else {
            BigDecimal bg = new BigDecimal((double) women / men).setScale(1, RoundingMode.UP);
            re = "1:" + bg;
        }
        return re;
    }

    @Override
    public String sexRatioTwo(String cname) throws Exception {
        Map<String, Long> map = sexRatio(cname);
        long men = map.get("men");
        long women = map.get("women");
        return men + ":" + women;
    }

    @Override
    public Boolean updateClazzWithGrade(String cname, String gname) throws Exception {

//        //  校验有没有这个班级
//        if (StringUtils.isBlank(cname) || existByName(cname)) {
//            throw new Exception("班级id输入有误或不存在");
//        }

        //检验班级
        Clazz clazz = clazzRepo.findByCname(cname)
                .orElseThrow(() -> new Exception("班级有误或不存在"));

        // 校验有没有这个年级
//        if (StringUtils.isBlank(gname) || !gradeService.existByName(gname)) {
//            throw new Exception("年级id输入有误或不存在");
//        }
        Grade grade = gradeRepo.findByGname(gname)
                .orElseThrow(() -> new Exception("年级有误或不存在"));

        // 校验这种情况是否需要更新
//        if (clazzRepo.findByGradeGname(gname)
//                .stream()
//                .map(Clazz::getCname)
//                .anyMatch((e)->e.equals(cname))) { throw new Exception("不需要更新"); }
        if (existByCnameAndGname(cname, gname)) {
            throw new Exception("不需要更新");
        }


        //重置clazz的年级
        clazz.setGrade(grade);

        //
        System.out.println(cname);
        System.out.println(clazz.getGrade().getGname());
        //
        //更新
        clazzRepo.save(clazz);
        return true;
    }

    @Override
    public Map<String, Set<Clazz>> classify() {
        //所有的年级
        List<Grade> gradeList = gradeRepo.findAll();
        Map<String, Set<Clazz>> collect = gradeList.stream()
                .collect(Collectors.toMap(Grade::getGname, Grade::getClazzList));
        return collect;
    }


    @Override
    public Boolean existByName(String cname) {
        Optional<Clazz> optionalClazz = clazzRepo.findByCname(cname);
        return optionalClazz.isPresent();
    }

    @Override
    public Boolean existByCnameAndGname(String cname, String gname) {
        Optional<Clazz> optionalClazz = clazzRepo.findByCnameAndGradeGname(cname, gname);
        return optionalClazz.isPresent();
    }
}
import com.example.demo.dao.dbo.Clazz;
import com.example.demo.dao.dbo.Grade;
import com.example.demo.dao.repo.GradeRepo;
import com.example.demo.service.GradeService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;

@Service
public class GradeServiceImpl implements GradeService {

    GradeRepo gradeRepo;

    @Autowired
    public GradeServiceImpl(GradeRepo gradeRepo) {
        this.gradeRepo = gradeRepo;
    }
    
    @Override
    public Map<String, Set<Clazz>> classify() {
        return gradeRepo.findAll()
                .stream()
                .collect(Collectors.toMap(Grade::getGname,Grade::getClazzList));
    }

    @Override
    public Boolean existByName(String gname) {
        Optional<Grade> optionalClazz = gradeRepo.findByGname(gname);
        return optionalClazz.isPresent();
    }
}

注意:java版本为8,

注意:多使用流的操作!流的操作在网上只有基础操作,但是有些需求可能是基础操作做不到的,但是实际上可以通过流做到的,那么这些就需要你下去多琢磨了。

另外:

推荐这样的写法,不要在一行显示,这样idea还会给你一些辅助,告诉你结束了这行以后它是什么样子的一个数据。

 


【忽然想到,在代码中有一个没用到的util,DateUtil,本来想的是存储的String类型的时间数据,后来改了,就没用到了,可以当做参考,另外,我是故意没有去操作生日,没有去想一些有关年龄和生日的业务,所以如果有需要,请自己整吧,哈哈哈】

application.properties想了想还是写出来吧,毕竟我自己也在这里花过不少时间:

spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/demo?serverTimezone=Asia/Shanghai
spring.datasource.username=root
spring.datasource.password=password
# 指定数据源类型
spring.jpa.database=MYSQL
# 指定是否在控制台输出SQL语句
spring.jpa.show-sql=true
# 支持自动建表 Hibernate ddl auto (create, create-drop, update)
spring.jpa.hibernate.ddl-auto=update
# 命名策略(Naming strategy):用于指定实体与表映射的命名规范
spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
spring.jpa.hibernate.naming.implicit-strategy=org.hibernate.boot.model.naming.ImplicitNamingStrategyJpaCompliantImpl
#spring.servlet.multipart.resolve-lazily=false
spring.datasource.platform=mysql

它里面有些东西用处不大,反正同学们自己改吧。


⑤一些测试的设计:

本来觉得这东西没什么说的,但是想了想,测试数据什么的,你可以自己在数据库根据实体类建表然后手动添加,(累死你)也可以在运行测试的时候自动添加(乐死了),这里建议你先添加年级,再添加班级,最后再添加学生(可惜的是我在测试代码里先写的学生,再写的班级,最后写的年级),但是不是绝对,只是为了方便起见。

具体的添加实体的方法都在代码中的Test1里面:

有需要的自己down吧。

还有一些有关impl的测试在Test2中,自己看吧。不多说了,该睡觉了。


晚安,打工人!


 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值