Java实体映射工具:MapStruct

Java实体映射工具:MapStruct当我们需要进行 JavaModel 之间的拷贝时 或者项目要求 JavaModel 需要严格区分为数据对象 DO 数据传输对象 DTO 和展示对象 VO 的时候 我们就不得不把一个实体中的属性映射到另一个实体中 最简单的做法就是写一个工具类 进行不断的 getter setter 这样虽然能完成要求但却写了很多冗余代码 维护起来相当恶心 所以这个时候就需要一款能自动映射实体属性的工具了 Spring 自带的 BeanUtils 工具类算是一款 但是它却不能自定义映射规则 ModelMapper 也是一款映射工具

        MapStruct版本:1.3.1.Final。


        当我们需要进行Java Model之间的拷贝时,或者项目要求Java Model需要严格区分为数据对象(DO)、数据传输对象(DTO)和展示对象(VO)的时候,我们就不得不把一个实体中的属性映射到另一个实体中。最简单的做法就是写一个工具类,进行不断的getter / setter,这样虽然能完成要求但却写了很多冗余代码,维护起来相当恶心。所以这个时候就需要一款能自动映射实体属性的工具了。

        Spring自带的BeanUtils工具类算是一款,但是它却不能自定义映射规则;ModelMapper也是一款映射工具框架,虽然它可以自定义映射规则,但写法上却复杂一些。而MapStruct作为一款优秀的Java实体映射工具来说,它也能够自定义映射规则,并且是通过注解的方式来实现的,源和目标看得很清楚明白。不同于BeanUtils和ModelMapper是通过反射在运行期生成代码从而导致性能不高,MapStruct是在编译期生成实现类映射代码,生成的代码就是普通的getter / setter代码,和原生使用的性能相差不大。


1 简单使用

        首先需要引入的依赖如下所示:

<dependency> <groupId>org.mapstruct</groupId> <artifactId>mapstruct</artifactId> <version>1.3.1.Final</version> </dependency> <dependency> <groupId>org.mapstruct</groupId> <artifactId>mapstruct-processor</artifactId> <version>1.3.1.Final</version> </dependency>

        除此之外如果使用的IDE是idea的话,还可以下载MapStruct的插件:

Java实体映射工具:MapStruct

        该插件可以动态地提示当前没有进行映射的字段,以及其他一些对MapStruct的支持(和Lombok不同,该插件不是必须安装的)。

        接着准备两个Java Model如下所示,一个DO,一个VO:

import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; import java.io.Serializable; @Data @NoArgsConstructor @AllArgsConstructor @Builder public class PersonDO implements Serializable { private static final long serialVersionUID = -0L; private Long personId; private String name; private Integer sex; private Integer age; private String address; } 
import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; import java.io.Serializable; @Data @NoArgsConstructor @AllArgsConstructor @Builder public class PersonVO implements Serializable { private static final long serialVersionUID = L; private Long id; private String name; private String sex; private Integer age; private String address; } 

        其中用到了Lombok的注解来简化编程,详见我的另一篇文章《Lombok概述》。在完成了上述准备之后,就可以进行MapStruct的开发使用了。

        我们现在是要把PersonDO转成PersonVO,首先需要写一个接口,如下所示:

import org.mapstruct.Mapper; import org.mapstruct.factory.Mappers; @Mapper public interface PersonMapper { PersonMapper INSTANCE = Mappers.getMapper(PersonMapper.class); PersonVO personDO2VO(PersonDO personDO); }

        之后需要进行打包编译,在对属性进行变动后都要进行打包编译,以此来生成新的实现类。MapStruct并不能及时地反映出属性的变更,比方说接口提供方变更了Model的属性,而调用方只有等到打包编译的时候才能提示出错误,这也许是MapStruct为数不多的缺点了吧,但介于MapStruct是在编译期生成映射代码的机制,这点也无可厚非,只要多加留意即可。

        生成的实现类如下所示:

import javax.annotation.Generated; @Generated( value = "org.mapstruct.ap.MappingProcessor", date = "2020-02-08T18:46:28+0800", comments = "version: 1.3.1.Final, compiler: javac, environment: Java 1.8.0_201 (Oracle Corporation)" ) public class PersonMapperImpl implements PersonMapper { @Override public PersonVO personDO2VO(PersonDO personDO) { if ( personDO == null ) { return null; } PersonVO personVO = new PersonVO(); personVO.setName( personDO.getName() ); if ( personDO.getSex() != null ) { personVO.setSex( String.valueOf( personDO.getSex() ) ); } personVO.setAge( personDO.getAge() ); personVO.setAddress( personDO.getAddress() ); return personVO; } }

        由上可以看到,生成的实现类就是通过getter / setter方法来实现的,不损失性能。由此可见我们只需要写一个接口,并定义好映射规则的方法就行了,不需要再写其他的代码。相应的测试代码如下所示:

PersonDO personDO = PersonDO.builder().personId(1L).name("Robert Hou").sex(1).age(24).address("Beijing").build(); PersonVO personVO = PersonMapper.INSTANCE.personDO2VO(personDO); System.out.println(personVO);

        运行结果如下:

PersonVO(id=null, name=Robert Hou, sex=1, age=24, address=Beijing)

2 Spring注入

        除了上节的在PersonMapper映射接口中声明INSTANCE的方式来进行调用之外,MapStruct也同时支持Spring的依赖注入机制,如下所示:

import org.mapstruct.Mapper; @Mapper(componentModel = "spring") public interface PersonMapper { PersonVO personDO2VO(PersonDO personDO); }

        只需要在@Mapper注解中添加componentModel配置项,并设置为“spring”即可。生成的实现类如下:

import javax.annotation.Generated; import org.springframework.stereotype.Component; @Generated( value = "org.mapstruct.ap.MappingProcessor", date = "2020-02-08T18:47:41+0800", comments = "version: 1.3.1.Final, compiler: javac, environment: Java 1.8.0_201 (Oracle Corporation)" ) @Component public class PersonMapperImpl implements PersonMapper { @Override public PersonVO personDO2VO(PersonDO personDO) { if ( personDO == null ) { return null; } PersonVO personVO = new PersonVO(); personVO.setName( personDO.getName() ); if ( personDO.getSex() != null ) { personVO.setSex( String.valueOf( personDO.getSex() ) ); } personVO.setAge( personDO.getAge() ); personVO.setAddress( personDO.getAddress() ); return personVO; } }

        可以看到是对该类添加了@Component注解,注册成为了一个Bean,之后就可以通过@Autowired注解来进行调用了。相应的测试代码如下所示:

import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; @SpringBootTest class TestApplicationTests { @Autowired private PersonMapper personMapper; @Test void mapperTest() { PersonDO personDO = PersonDO.builder().personId(1L).name("Robert Hou").sex(1).age(24).address("Beijing").build(); PersonVO personVO = personMapper.personDO2VO(personDO); System.out.println(personVO); } }

3 自定义映射

        由上面的映射规则可知,MapStruct默认只会对同名的属性进行映射,对于不同名的属性则不会映射,如PersonDO的personId属性就没有映射到PersonVO的id属性中。这种情况下就需要手动选择映射属性,如下所示:

import org.mapstruct.Mapper; import org.mapstruct.Mapping; import org.mapstruct.Mappings; @Mapper(componentModel = "spring") public interface PersonMapper { @Mappings({ @Mapping(source = "personId", target = "id") }) PersonVO personDO2VO(PersonDO personDO); }

        如果映射属性只有一个的话,则可以不用使用@Mappings注解而只使用@Mapping注解也都是可以的。这回再来看一下生成的实现类代码:

import javax.annotation.Generated; import org.springframework.stereotype.Component; @Generated( value = "org.mapstruct.ap.MappingProcessor", date = "2020-02-08T18:48:47+0800", comments = "version: 1.3.1.Final, compiler: javac, environment: Java 1.8.0_201 (Oracle Corporation)" ) @Component public class PersonMapperImpl implements PersonMapper { @Override public PersonVO personDO2VO(PersonDO personDO) { if ( personDO == null ) { return null; } PersonVO personVO = new PersonVO(); personVO.setId( personDO.getPersonId() ); personVO.setName( personDO.getName() ); if ( personDO.getSex() != null ) { personVO.setSex( String.valueOf( personDO.getSex() ) ); } personVO.setAge( personDO.getAge() ); personVO.setAddress( personDO.getAddress() ); return personVO; } }

        在第20行可以看到对Id属性也进行了映射。


4 映射集合

        MapStruct也支持对集合的映射,写起来也相当简单。比如说我们现在需要将一个PersonDO的List集合转换成PersonVO的List集合,MapStruct的写法如下所示:

import org.mapstruct.Mapper; import org.mapstruct.Mapping; import org.mapstruct.Mappings; import java.util.List; @Mapper(componentModel = "spring") public interface PersonMapper { @Mappings({ @Mapping(source = "personId", target = "id") }) PersonVO personDO2VO(PersonDO personDO); List<PersonVO> personDOs2VOs(List<PersonDO> personDOList); }

        可以看到只需要在第15行添加一个对集合进行映射的方法就行了,并不需要显示地调用personDO2VO方法,这完全得益于MapStruct的自动类型探测,生成的实现类代码如下所示:

import java.util.ArrayList; import java.util.List; import javax.annotation.Generated; import org.springframework.stereotype.Component; @Generated( value = "org.mapstruct.ap.MappingProcessor", date = "2020-02-08T18:51:17+0800", comments = "version: 1.3.1.Final, compiler: javac, environment: Java 1.8.0_201 (Oracle Corporation)" ) @Component public class PersonMapperImpl implements PersonMapper { @Override public PersonVO personDO2VO(PersonDO personDO) { if ( personDO == null ) { return null; } PersonVO personVO = new PersonVO(); personVO.setId( personDO.getPersonId() ); personVO.setName( personDO.getName() ); if ( personDO.getSex() != null ) { personVO.setSex( String.valueOf( personDO.getSex() ) ); } personVO.setAge( personDO.getAge() ); personVO.setAddress( personDO.getAddress() ); return personVO; } @Override public List<PersonVO> personDOs2VOs(List<PersonDO> personDOList) { if ( personDOList == null ) { return null; } List<PersonVO> list = new ArrayList<PersonVO>( personDOList.size() ); for ( PersonDO personDO : personDOList ) { list.add( personDO2VO( personDO ) ); } return list; } }

        由上面的第41行代码可以看到personDOs2VOs方法实现了对personDO2VO方法的调用。


5 忽略映射

        在某些情况下我们不需要对某些字段进行映射,MapStruct也是支持的。比方说现在不需要对address字段进行映射,那么写法如下所示:

import org.mapstruct.Mapper; import org.mapstruct.Mapping; import org.mapstruct.Mappings; @Mapper(componentModel = "spring") public interface PersonMapper { @Mappings({ @Mapping(source = "personId", target = "id"), @Mapping(target = "address", ignore = true) }) PersonVO personDO2VO(PersonDO personDO); }

        如第10行代码所示,将ignore选项值赋为true即可。这样的话生成的实现类就不会对address字段进行映射了。


6 多参数映射

        有些时候我们的入参不止一个,而是有多个,这样的情况MapStruct也是支持的,如下所示:

import org.mapstruct.Mapper; import org.mapstruct.Mapping; import org.mapstruct.Mappings; @Mapper(componentModel = "spring") public interface PersonMapper { @Mappings({ @Mapping(source = "personDO1.personId", target = "id"), @Mapping(source = "personDO1.name", target = "name"), @Mapping(source = "personDO1.sex", target = "sex"), @Mapping(source = "personDO2.age", target = "age"), @Mapping(source = "personDO2.address", target = "address") }) PersonVO personDO2VO(PersonDO personDO1, PersonDO personDO2); }

        入参有两个PersonDO,将第一个PersonDO的personId、name和sex属性赋值给PersonVO,而将第二个PersonDO的age和address属性赋值给PersonVO。需要注意的是如果多个入参的属性名相同,则需要起别名来进行区别,否则编译会报错。生成的实现类代码如下:

import javax.annotation.Generated; import org.springframework.stereotype.Component; @Generated( value = "org.mapstruct.ap.MappingProcessor", date = "2020-02-08T20:58:00+0800", comments = "version: 1.3.1.Final, compiler: javac, environment: Java 1.8.0_201 (Oracle Corporation)" ) @Component public class PersonMapperImpl implements PersonMapper { @Override public PersonVO personDO2VO(PersonDO personDO1, PersonDO personDO2) { if ( personDO1 == null && personDO2 == null ) { return null; } PersonVO personVO = new PersonVO(); if ( personDO1 != null ) { personVO.setName( personDO1.getName() ); personVO.setId( personDO1.getPersonId() ); if ( personDO1.getSex() != null ) { personVO.setSex( String.valueOf( personDO1.getSex() ) ); } } if ( personDO2 != null ) { personVO.setAddress( personDO2.getAddress() ); personVO.setAge( personDO2.getAge() ); } return personVO; } }

7 映射规则

        对于一些简单的类型转换,例如int转String,boolean转Boolean等等,MapStruct都可以自动完成。在上面的例子中也有所体现,例如PersonDO的sex字段是Integer类型的,而PersonVO的sex字段是String类型的,MapStruct通过String.valueOf的方式完成了转换,不需要使用者操心。

        而对于Date和String类型之间的相互转换,MapStruct也是支持的,这里我们往PersonDO中新添加一个Date类型的birthday属性,在PersonVO中新添加一个String类型的birthday属性,两者进行映射。然后MapStruct的接口类代码如下所示:

import org.mapstruct.Mapper; import org.mapstruct.Mapping; import org.mapstruct.Mappings; @Mapper(componentModel = "spring") public interface PersonMapper { @Mappings({ @Mapping(source = "personId", target = "id"), @Mapping(source = "birthday", target = "birthday", dateFormat = "yyyy-MM-dd HH:mm:ss") }) PersonVO personDO2VO(PersonDO personDO); } 

        通过dateFormat配置项可以配置日期的格式,生成的实现类如下:

import java.text.SimpleDateFormat; import javax.annotation.Generated; import org.springframework.stereotype.Component; @Generated( value = "org.mapstruct.ap.MappingProcessor", date = "2020-02-08T19:17:11+0800", comments = "version: 1.3.1.Final, compiler: javac, environment: Java 1.8.0_201 (Oracle Corporation)" ) @Component public class PersonMapperImpl implements PersonMapper { @Override public PersonVO personDO2VO(PersonDO personDO) { if ( personDO == null ) { return null; } PersonVO personVO = new PersonVO(); if ( personDO.getBirthday() != null ) { personVO.setBirthday( new SimpleDateFormat( "yyyy-MM-dd HH:mm:ss" ).format( personDO.getBirthday() ) ); } personVO.setId( personDO.getPersonId() ); personVO.setName( personDO.getName() ); if ( personDO.getSex() != null ) { personVO.setSex( String.valueOf( personDO.getSex() ) ); } personVO.setAge( personDO.getAge() ); personVO.setAddress( personDO.getAddress() ); return personVO; } }

        由上面第22行代码可以看到,MapStruct是通过SimpleDateFormat的方式完成的转换。另外MapStruct还提供了一个numberFormat关于数据精度的配置,读者可自行尝试,这里就不再演示了。

        有些情况下可能需要完成更加复杂、更加定制化的映射规则,这时候就需要我们自己来写映射代码了,这在MapStruct中实现也非常容易。现在我们不需要从PersonDO中的sex字段映射到PersonVO中的sex字段,而是通过身份证号的倒数第二位来进行判断,如果为奇数,则为男,反之则为女。继续往PersonDO中新添加一个String类型的idNumber属性,然后需要写一个转换的工具方法:

public class PersonUtils { private PersonUtils() { } public static String getSex(String idNumber) { if (idNumber == null) { return null; } //截取身份证倒数第二位数字 String in = idNumber.substring(idNumber.length() - 2, idNumber.length() - 1); int i = Integer.parseInt(in); //如果为奇数,则是男,反之则为女 return (i & 1) == 1 ? "男" : "女"; } } 

        MapStruct的接口类改造如下:

import org.mapstruct.Mapper; import org.mapstruct.Mapping; import org.mapstruct.Mappings; @Mapper(componentModel = "spring", imports = PersonUtils.class) public interface PersonMapper { @Mappings({ @Mapping(source = "personId", target = "id"), @Mapping(source = "birthday", target = "birthday", dateFormat = "yyyy-MM-dd HH:mm:ss"), @Mapping(target = "sex", expression = "java(PersonUtils.getSex(personDO.getIdNumber()))") }) PersonVO personDO2VO(PersonDO personDO); } 

        首先需要在类上的@Mapper注解中加上imports选项来引入这个工具类,然后通过在@Mapping注解中添加expression选项来完成调用,如第11行所示。需要注意的是expression配置项中首先需要写“java()”,然后再在其中写具体的调用代码。生成的实现类代码如下所示:

import java.text.SimpleDateFormat; import javax.annotation.Generated; import org.springframework.stereotype.Component; @Generated( value = "org.mapstruct.ap.MappingProcessor", date = "2020-02-08T20:04:35+0800", comments = "version: 1.3.1.Final, compiler: javac, environment: Java 1.8.0_201 (Oracle Corporation)" ) @Component public class PersonMapperImpl implements PersonMapper { @Override public PersonVO personDO2VO(PersonDO personDO) { if ( personDO == null ) { return null; } PersonVO personVO = new PersonVO(); if ( personDO.getBirthday() != null ) { personVO.setBirthday( new SimpleDateFormat( "yyyy-MM-dd HH:mm:ss" ).format( personDO.getBirthday() ) ); } personVO.setId( personDO.getPersonId() ); personVO.setName( personDO.getName() ); personVO.setAge( personDO.getAge() ); personVO.setAddress( personDO.getAddress() ); personVO.setSex( PersonUtils.getSex(personDO.getIdNumber()) ); return personVO; } }

        如上第29行所示,通过调用PersonUtils的工具类方法,完成了sex字段的映射。同时如果使用的Java版本是8或者以上,那么可以使用从Java 8开始支持的接口中的default语句来简化编程,不用再单独写一个工具类了。比如说现在我们需要将PersonDO中的age字段的值加上10,然后赋值给PersonVO中的age字段。如下所示:

import org.mapstruct.Mapper; import org.mapstruct.Mapping; import org.mapstruct.Mappings; @Mapper(componentModel = "spring", imports = PersonUtils.class) public interface PersonMapper { @Mappings({ @Mapping(source = "personId", target = "id"), @Mapping(source = "birthday", target = "birthday", dateFormat = "yyyy-MM-dd HH:mm:ss"), @Mapping(target = "sex", expression = "java(PersonUtils.getSex(personDO.getIdNumber()))") }) PersonVO personDO2VO(PersonDO personDO); default Integer addAge(Integer age) { return age + 10; } }

        不需要显式地进行调用,MapStruct会自己推测出来。生成的实现类代码如下:

import java.text.SimpleDateFormat; import javax.annotation.Generated; import org.springframework.stereotype.Component; @Generated( value = "org.mapstruct.ap.MappingProcessor", date = "2020-02-08T20:32:12+0800", comments = "version: 1.3.1.Final, compiler: javac, environment: Java 1.8.0_201 (Oracle Corporation)" ) @Component public class PersonMapperImpl implements PersonMapper { @Override public PersonVO personDO2VO(PersonDO personDO) { if ( personDO == null ) { return null; } PersonVO personVO = new PersonVO(); if ( personDO.getBirthday() != null ) { personVO.setBirthday( new SimpleDateFormat( "yyyy-MM-dd HH:mm:ss" ).format( personDO.getBirthday() ) ); } personVO.setId( personDO.getPersonId() ); personVO.setName( personDO.getName() ); personVO.setAge( addAge( personDO.getAge() ) ); personVO.setAddress( personDO.getAddress() ); personVO.setSex( PersonUtils.getSex(personDO.getIdNumber()) ); return personVO; } }

        从第26行代码可以看到,显式调用了addAge方法完成了转换的操作。但是有一点需要注意,MapStruct是通过入参和出参的类型进行判断,从而进行赋值的。拿这个例子来说,MapStruct会把所有的源Model类型为Integer和目标Model类型为Integer的属性都添加上这个addAge方法,这往往会造成把不想要转换的属性也给转换了。在这种情况下就不能使用default语句了,而转而使用上文所说的expression配置项,写一个工具类方法,显示地对属性进行调用即可,这点需要多加留意。


8 默认值和常量

        同时MapStruct也支持默认值和常量,如下所示:

import org.mapstruct.Mapper; import org.mapstruct.Mapping; import org.mapstruct.Mappings; @Mapper(componentModel = "spring") public interface PersonMapper { @Mappings({ @Mapping(source = "personId", target = "id"), @Mapping(source = "birthday", target = "birthday", dateFormat = "yyyy-MM-dd HH:mm:ss"), @Mapping(target = "sex", constant = "男"), @Mapping(target = "address", defaultValue = "中国"), }) PersonVO personDO2VO(PersonDO personDO); }

        如上第11行和第12行代码所示,sex字段常量赋值为“男”,address字段使用了默认值,即该字段如果值为null的情况下赋值为“中国”,不为null则照常进行赋值。生成的实现类代码如下:

import java.text.SimpleDateFormat; import javax.annotation.Generated; import org.springframework.stereotype.Component; @Generated( value = "org.mapstruct.ap.MappingProcessor", date = "2020-02-08T21:20:11+0800", comments = "version: 1.3.1.Final, compiler: javac, environment: Java 1.8.0_201 (Oracle Corporation)" ) @Component public class PersonMapperImpl implements PersonMapper { @Override public PersonVO personDO2VO(PersonDO personDO) { if ( personDO == null ) { return null; } PersonVO personVO = new PersonVO(); if ( personDO.getBirthday() != null ) { personVO.setBirthday( new SimpleDateFormat( "yyyy-MM-dd HH:mm:ss" ).format( personDO.getBirthday() ) ); } personVO.setId( personDO.getPersonId() ); personVO.setName( personDO.getName() ); personVO.setAge( personDO.getAge() ); if ( personDO.getAddress() != null ) { personVO.setAddress( personDO.getAddress() ); } else { personVO.setAddress( "中国" ); } personVO.setSex( "男" ); return personVO; } } 

9 空Model返回

        通过上面的例子可以看到,当入参Model本身为null的时候,则直接返回null赋值给出参Model。有些情况下我们想返回一个空属性的Model而不是null,这样可以不用再在后续进行空指针判断。如下所示:

import org.mapstruct.Mapper; import org.mapstruct.Mapping; import org.mapstruct.Mappings; import org.mapstruct.NullValueMappingStrategy; @Mapper(componentModel = "spring", nullValueMappingStrategy = NullValueMappingStrategy.RETURN_DEFAULT) public interface PersonMapper { @Mappings({ @Mapping(source = "personId", target = "id"), @Mapping(source = "birthday", target = "birthday", dateFormat = "yyyy-MM-dd HH:mm:ss"), @Mapping(target = "sex", constant = "男"), @Mapping(target = "address", defaultValue = "中国"), }) PersonVO personDO2VO(PersonDO personDO); }

        在类上的@Mapper注解中加上nullValueMappingStrategy配置项,并赋值为NullValueMappingStrategy.RETURN_DEFAULT即可,默认为RETURN_NULL,即返回null。生成的实现类代码如下:

import java.text.SimpleDateFormat; import javax.annotation.Generated; import org.springframework.stereotype.Component; @Generated( value = "org.mapstruct.ap.MappingProcessor", date = "2020-02-08T21:24:37+0800", comments = "version: 1.3.1.Final, compiler: javac, environment: Java 1.8.0_201 (Oracle Corporation)" ) @Component public class PersonMapperImpl implements PersonMapper { @Override public PersonVO personDO2VO(PersonDO personDO) { PersonVO personVO = new PersonVO(); if ( personDO != null ) { if ( personDO.getBirthday() != null ) { personVO.setBirthday( new SimpleDateFormat( "yyyy-MM-dd HH:mm:ss" ).format( personDO.getBirthday() ) ); } personVO.setId( personDO.getPersonId() ); personVO.setName( personDO.getName() ); personVO.setAge( personDO.getAge() ); if ( personDO.getAddress() != null ) { personVO.setAddress( personDO.getAddress() ); } else { personVO.setAddress( "中国" ); } } personVO.setSex( "男" ); return personVO; } }

        可以看到如果PersonDO为null的话,会返回一个空属性的PersonVO(赋值为常量的情况除外),而不是直接返回null。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/231516.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • 卡盟平台_卡盟做淘宝对接好吗

    卡盟平台_卡盟做淘宝对接好吗简介:商城风格,三内页模板,全修复无BUG,一键装修主站,一键对接货源,自定义后台登录背景,前台风格自定义背景等,已集成易接口对接易充值接口,修复BUG等上传好后解压文件修改0.system.conf文件输入自己绑定的域名创建一个数据库systemroot密码改为root这里演示默认,运营的时候自行修改密码导入数据库打开apache的配置把最后一行修改为IncludeOptional/home/vhost/*.conf在计划任务里面添加Shell脚本任务名称随便设置执行周期为1

    2022年8月12日
    4
  • DB2 SQL存储过程语法

    DB2 SQL存储过程语法

    2021年5月6日
    110
  • dijkstra算法求最短路_图论的最短路问题

    dijkstra算法求最短路_图论的最短路问题原题链接战争中保持各个城市间的连通性非常重要。本题要求你编写一个报警程序,当失去一个城市导致国家被分裂为多个无法连通的区域时,就发出红色警报。注意:若该国本来就不完全连通,是分裂的k个区域,而失去一个城市并不改变其他城市之间的连通性,则不要发出警报。输入格式:输入在第一行给出两个整数N(0 < N ≤ 500)和M(≤ 5000),分别为城市个数(于是默认城市从0到N-1编号)和连接两城市的通路条数。随后M行,每行给出一条通路所连接的两个城市的编号,其间以1个空格分隔。在城市信息之后给出被攻占的

    2022年8月9日
    9
  • 小兔子跷跷板flash动画制作教程「建议收藏」

    小兔子跷跷板flash动画制作教程「建议收藏」 [1b]制作步骤:[/1b]  1、新建一个默认大小的Flash文档。先画天空背景,用矩形工具画一个矩形,在对齐面板中按下“相对于舞台”,再点“匹配宽和高”按钮,最后点“垂直中齐”和“水平中齐”按钮。与舞台对齐之后给矩形设置如下从白色到天蓝色的渐变,方式为“线性”。[img]/uploads/allimg/081209/2224570.jpg[/img] 图1   2、草…

    2022年4月28日
    61
  • 怎么判断草图完全约束_算法基础课acwing下载

    怎么判断草图完全约束_算法基础课acwing下载爱丽丝和鲍勃正在玩以下游戏。首先,爱丽丝绘制一个 N 个点 M 条边的有向图。然后,鲍勃试图毁掉它。在每一步操作中,鲍勃都可以选取一个点,并将所有射入该点的边移除或者将所有从该点射出的边移除。已知,对于第 i 个点,将所有射入该点的边移除所需的花费为 W+i,将所有从该点射出的边移除所需的花费为 W−i。鲍勃需要将图中的所有边移除,并且还要使花费尽可能少。请帮助鲍勃计算最少花费。输入格式第一行包含 N 和 M。第二行包含 N 个正整数,第 i 个为 W+i。第三行包含 N 个正整数,第.

    2022年8月9日
    3
  • 怎么同时运行两个tomcat?

    怎么同时运行两个tomcat?转载至:http://ask.zol.com.cn/x/4522378.html这几天由于在搞那个jenkins的自动部署项目所以要使用到两个tomcat(因为一个tomcat不能同时开着两个项目),一个作为jenkins服务器,一个作为项目部署服务器,所以找了一些资料看看一台电脑怎么运行两个tomcat。第一步:先下载两个tomcat(不同版本的也行,笔者用的是一个tomcat7,一个…

    2022年6月15日
    37

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号