Docker Compose deploys SpringBoot project

DockerCompose deploys SpringBoot project

Prepare SpringBoot project

List of project catalogs
Project source code Gitee address

Prepare the database

CREATE TABLE `t_user`
(
    `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
    `username` varchar(50) NOT NULL DEFAULT '' COMMENT 'username',
    `password` varchar(50) NOT NULL DEFAULT '' COMMENT 'password',
    `sex` tinyint(4) NOT NULL DEFAULT '0' COMMENT 'Sex 0=female 1=male ',
    `deleted` tinyint(4) unsigned NOT NULL DEFAULT '0' COMMENT 'Delete flag, default 0 not delete, 1 delete',
    `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'update time',
    `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'create time',
    PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT='user table';

Add pom dependencies

<parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.5.6</version>
        <!--<version>2.3.10.RELEASE</version>-->
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <junit.version>4.12</junit.version>
        <log4j.version>1.2.17</log4j.version>
        <lombok.version>1.16.18</lombok.version>
        <mysql.version>5.1.47</mysql.version>
        <druid.version>1.1.16</druid.version>
        <mapper.version>4.1.5</mapper.version>
        <mybatis.spring.boot.version>1.3.0</mybatis.spring.boot.version>
    </properties>

    <dependencies>
        <!--guava Bloom filter that comes with Google's open source Guava -->
        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>23.0</version>
        </dependency>
        <!-- redisson -->
        <dependency>
            <groupId>org.redisson</groupId>
            <artifactId>redisson</artifactId>
            <version>3.13.4</version>
        </dependency>
        <!--SpringBoot common dependency module-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <!--swagger2-->
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <version>2.9.2</version>
        </dependency>
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger-ui</artifactId>
            <version>2.9.2</version>
        </dependency>
        <!--SpringBoot and Redis integration dependencies -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <!--springCache-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-cache</artifactId>
        </dependency>
        <!--springCache connection pool dependency package -->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
        </dependency>
        <!-- jedis -->
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
            <version>3.1.0</version>
        </dependency>
        <!--Mysql database driver-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.47</version>
        </dependency>
        <!--SpringBoot integrated druid connection pool-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.10</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>${druid.version}</version>
        </dependency>
        <!--mybatis and springboot integration-->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>${mybatis.spring.boot.version}</version>
        </dependency>
        <dependency>
            <groupId>commons-codec</groupId>
            <artifactId>commons-codec</artifactId>
            <version>1.10</version>
        </dependency>
        <!--Common basic configuration junit/devtools/test/log4j/lombok/hutool-->
        <!--hutool-->
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.2.3</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>${junit.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>${log4j.version}</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>${lombok.version}</version>
            <optional>true</optional>
        </dependency>
        <!--persistence-->
        <dependency>
            <groupId>javax.persistence</groupId>
            <artifactId>persistence-api</artifactId>
            <version>1.0.2</version>
        </dependency>
        <!--General Mapper-->
        <dependency>
            <groupId>tk.mybatis</groupId>
            <artifactId>mapper</artifactId>
            <version>${mapper.version}</version>
        </dependency>
    </dependencies>

Main startup class

@SpringBootApplication
@MapperScan("com.zhuang.docker.mapper")
public class DockerBootApplication {<!-- -->
    
    public static void main(String[] args) {<!-- -->
        SpringApplication.run(DockerBootApplication.class, args);
    }

}

Config layer

RedisConfig

@Configuration
@Slf4j
public class RedisConfig {<!-- -->
    /**
     * @param lettuceConnectionFactory
     * @return redis serialized tool configuration class, please be sure to open the configuration below
     * 127.0.0.1:6379> keys *
     * 1) "ord:102" serialized
     * 2) "\xac\xed\x00\x05t\x00\aord:102" wild, not serialized
     */
    @Bean
    public RedisTemplate<String, Serializable> redisTemplate(LettuceConnectionFactory lettuceConnectionFactory) {<!-- -->
        RedisTemplate<String, Serializable> redisTemplate = new RedisTemplate<>();

        redisTemplate.setConnectionFactory(lettuceConnectionFactory);
        //Set key serialization method string
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        //Set the serialization method of value json
        redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());

        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());

        redisTemplate.afterPropertiesSet();

        return redisTemplate;
    }

}

SwaggerConfig

@Configuration
@EnableSwagger2
public class SwaggerConfig {<!-- -->

    @Value("${spring. swagger2. enabled}")
    private Boolean enabled;

    @Bean
    public Docket createRestApi() {<!-- -->
        return new Docket(DocumentationType. SWAGGER_2)
                .apiInfo(apiInfo())
                .enable(enabled)
                .select()
                .apis(RequestHandlerSelectors.basePackage("com.zhuang.docker")) //your own package
                .paths(PathSelectors. any())
                .build();
    }

    public ApiInfo apiInfo() {<!-- -->
        return new ApiInfoBuilder()
                .title("Kang Xiaozhuang's Docker project" + "\t" + new SimpleDateFormat("yyyy-MM-dd").format(new Date()))
                .description("docker-compose")
                .version("1.0")
                .termsOfServiceUrl("https://itkxz.cn/")
                .build();
    }
}

Entity layer

@Table(name = "t_user")
public class User {<!-- -->
    @Id
    @GeneratedValue(generator = "JDBC")
    private Integer id;

    /**
     * username
     */
    private String username;

    /**
     * password
     */
    private String password;

    /**
     * Gender 0=female 1=male
     */
    private Byte sex;

    /**
     * Delete flag, default 0 is not deleted, 1 is deleted
     */
    private Byte deleted;

    /**
     * update time
     */
    @Column(name = "update_time")
    private Date updateTime;

    /**
     * Creation time
     */
    @Column(name = "create_time")
    private Date createTime;

    /**
     * @return id
     */
    public Integer getId() {<!-- -->
        return id;
    }

    /**
     * @param id
     */
    public void setId(Integer id) {<!-- -->
        this.id = id;
    }

    /**
     * get username
     *
     * @return username - username
     */
    public String getUsername() {<!-- -->
        return username;
    }

    /**
     * set username
     *
     * @param username username
     */
    public void setUsername(String username) {<!-- -->
        this. username = username;
    }

    /**
     * get password
     *
     * @return password - the password
     */
    public String getPassword() {<!-- -->
        return password;
    }

    /**
     * set password
     *
     * @param password password
     */
    public void setPassword(String password) {<!-- -->
        this.password = password;
    }

    /**
     * Get gender 0=female 1=male
     *
     * @return sex - sex 0=female 1=male
     */
    public Byte getSex() {<!-- -->
        return sex;
    }

    /**
     * Set gender 0=female 1=male
     *
     * @param sex sex 0=female 1=male
     */
    public void setSex(Byte sex) {<!-- -->
        this. sex = sex;
    }

    /**
     * Get the delete flag, the default is 0 to not delete, 1 to delete
     *
     * @return deleted - delete flag, default 0 is not deleted, 1 is deleted
     */
    public Byte getDeleted() {<!-- -->
        return deleted;
    }

    /**
     * Set the delete flag, default 0 is not deleted, 1 is deleted
     *
     * @param deleted delete flag, default 0 not delete, 1 delete
     */
    public void setDeleted(Byte deleted) {<!-- -->
        this.deleted = deleted;
    }

    /**
     * get update time
     *
     * @return update_time - update time
     */
    public Date getUpdateTime() {<!-- -->
        return updateTime;
    }

    /**
     * Set update time
     *
     * @param updateTime update time
     */
    public void setUpdateTime(Date updateTime) {<!-- -->
        this. updateTime = updateTime;
    }

    /**
     * Get creation time
     *
     * @return create_time - the creation time
     */
    public Date getCreateTime() {<!-- -->
        return createTime;
    }

    /**
     * Set creation time
     *
     * @param createTime creation time
     */
    public void setCreateTime(Date createTime) {<!-- -->
        this.createTime = createTime;
    }
}

UserDTO

@NoArgsConstructor
@AllArgsConstructor
@Data
@ApiModel(value = "User Information")
public class UserDTO implements Serializable {<!-- -->
    @ApiModelProperty(value = "User ID")
    private Integer id;

    @ApiModelProperty(value = "username")
    private String username;

    @ApiModelProperty(value = "Password")
    private String password;

    @ApiModelProperty(value = "Gender 0=Female 1=Male ")
    private Byte sex;

    @ApiModelProperty(value = "delete flag, default 0 is not deleted, 1 is deleted")
    private Byte deleted;

    @ApiModelProperty(value = "update time")
    private Date updateTime;

    @ApiModelProperty(value = "creation time")
    private Date createTime;

    /**
     * @return id
     */
    public Integer getId() {<!-- -->
        return id;
    }

    /**
     * @param id
     */
    public void setId(Integer id) {<!-- -->
        this.id = id;
    }

    /**
     * get username
     *
     * @return username - username
     */
    public String getUsername() {<!-- -->
        return username;
    }

    /**
     * set username
     *
     * @param username username
     */
    public void setUsername(String username) {<!-- -->
        this. username = username;
    }

    /**
     * get password
     *
     * @return password - the password
     */
    public String getPassword() {<!-- -->
        return password;
    }

    /**
     * set password
     *
     * @param password password
     */
    public void setPassword(String password) {<!-- -->
        this.password = password;
    }

    /**
     * Get gender 0=female 1=male
     *
     * @return sex - sex 0=female 1=male
     */
    public Byte getSex() {<!-- -->
        return sex;
    }

    /**
     * Set gender 0=female 1=male
     *
     * @param sex sex 0=female 1=male
     */
    public void setSex(Byte sex) {<!-- -->
        this. sex = sex;
    }

    /**
     * Get the delete flag, the default is 0 to not delete, 1 to delete
     *
     * @return deleted - delete flag, default 0 is not deleted, 1 is deleted
     */
    public Byte getDeleted() {<!-- -->
        return deleted;
    }

    /**
     * Set the delete flag, default 0 is not deleted, 1 is deleted
     *
     * @param deleted delete flag, default 0 not delete, 1 delete
     */
    public void setDeleted(Byte deleted) {<!-- -->
        this.deleted = deleted;
    }

    /**
     * get update time
     *
     * @return update_time - update time
     */
    public Date getUpdateTime() {<!-- -->
        return updateTime;
    }

    /**
     * Set update time
     *
     * @param updateTime update time
     */
    public void setUpdateTime(Date updateTime) {<!-- -->
        this. updateTime = updateTime;
    }

    /**
     * Get creation time
     *
     * @return create_time - the creation time
     */
    public Date getCreateTime() {<!-- -->
        return createTime;
    }

    /**
     * Set creation time
     *
     * @param createTime creation time
     */
    public void setCreateTime(Date createTime) {<!-- -->
        this.createTime = createTime;
    }

    @Override
    public String toString() {<!-- -->
        return "User{" +
                "id=" + id +
                ", username='" + username + '\'' +
                ", password='" + password + '\'' +
                ", sex=" + sex +
                '}';
    }
}

Mapper layer

public interface UserMapper extends Mapper<User> {<!-- -->
}

Service layer

@Service
@Slf4j
public class UserService {<!-- -->

    public static final String CACHE_KEY_USER = "user:";

    @Resource
    private UserMapper userMapper;
    @Resource
    private RedisTemplate redisTemplate;

    /**
     * addUser
     *
     * @param user User
     */
    public void addUser(User user) {<!-- -->
        //1 insert mysql first and succeed
        int i = userMapper.insertSelective(user);

        if (i > 0) {<!-- -->
            //2 You need to query mysql again to get the data back and it's ok
            user = userMapper.selectByPrimaryKey(user.getId());
            //3 Save the fished out user into redis to complete the data consistency of the new function.
            String key = CACHE_KEY_USER + user. getId();
            redisTemplate.opsForValue().set(key, user);
        }
    }

    /**
     * findUserById
     *
     * @param id Integer
     * @return User
     */
    public User findUserById(Integer id) {<!-- -->
        User user = null;
        String key = CACHE_KEY_USER + id;

        //1 First query from redis, if there is a direct return result, if not, then query mysql
        user = (User) redisTemplate.opsForValue().get(key);

        if (user == null) {<!-- -->
            //2 There is nothing in redis, continue to query mysql
            user = userMapper. selectByPrimaryKey(id);
            if (user == null) {<!-- -->
                //3.1 redis + mysql have no data
                //You specify the details to prevent multiple penetrations. We stipulate that the key that caused the penetration should be recorded and written back to redis
                return user;
            } else {<!-- -->
                //3.2 mysql has it, and the data needs to be written back to redis to ensure the next cache hit rate
                redisTemplate.opsForValue().set(key, user);
            }
        }
        return user;
    }

    /**
     * @param id
     */
    public void deleteUser(Integer id) {<!-- -->
        userMapper.deleteByPrimaryKey(id);
    }

    /**
     * @param user User
     */
    public void updateUser(User user) {<!-- -->
        userMapper. updateByPrimaryKey(user);
    }
}

Controller layer

@Api(description = "User Interface")
@RestController
@Slf4j
public class UserController {<!-- -->
    @Resource
    private UserService userService;

    @ApiOperation("New 3 records in the database")
    @RequestMapping(value = "/user/add", method = RequestMethod. POST)
    public void addUser() {<!-- -->
        for (int i = 1; i <= 3; i ++ ) {<!-- -->
            User user = new User();

            user.setUsername("zk" + i);
            user.setPassword(IdUtil.simpleUUID().substring(0, 6));
            user.setSex((byte) new Random().nextInt(2));

            userService. addUser(user);
        }
    }

    @ApiOperation("Delete 1 record")
    @RequestMapping(value = "/user/delete/{id}", method = RequestMethod. POST)
    public void deleteUser(@PathVariable Integer id) {<!-- -->
        userService.deleteUser(id);
    }


    @ApiOperation("Modify 1 record")
    @RequestMapping(value = "/user/update", method = RequestMethod. POST)
    public void updateUser(@RequestBody UserDTO userDTO) {<!-- -->
        User user = new User();
        BeanUtils. copyProperties(userDTO, user);
        userService. updateUser(user);
    }

    @ApiOperation("Query 1 record")
    @RequestMapping(value = "/user/find/{id}", method = RequestMethod.GET)
    public User findUserById(@PathVariable Integer id) {<!-- -->
        return userService. findUserById(id);
    }
}

Configuration file

server.port=6001
# =========================== alibaba.druid related configuration ====================== =
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://ip:3306/docker?useUnicode=true &characterEncoding=utf-8 &useSSL=false
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.druid.test-while-idle=false
# =========================== redis related configuration ======================
spring.redis.database=0
spring.redis.host=ip
spring.redis.port=6379
spring.redis.password=
spring.redis.lettuce.pool.max-active=8
spring.redis.lettuce.pool.max-wait=-1ms
spring.redis.lettuce.pool.max-idle=8
spring.redis.lettuce.pool.min-idle=0
# =========================== mybatis related configuration =====================
mybatis.mapper-locations=classpath:mapper/*.xml
mybatis.type-aliases-package=com.zhuang.docker.entity
# =========================== swagger=======================
spring.swagger2.enabled=true

Project run test

Prepare the database

Prepare Redis

Start the project successfully

browser test

http://localhost:6001/user/find/88

Visit the swagger page for testing

http://localhost:6001/swagger-ui.html

Test success

Query database inserted successfully

Redis inserted successfully

Deploy with Docker

docker run -p 3307:3306 --name mysql57 --privileged=true -v /mydata/mysql-master/log:/var/log/mysql -v /mydata/mysql-master/data:/var/ lib/mysql -v /mydata/mysql-master/conf:/etc/mysql -e MYSQL_ROOT_PASSWORD=root -d mysql:5.7

Enter the container bash

docker exec -it mysql57 /bin/bash

create database

mysql -uroot -p

create database docker;

CREATE TABLE `t_user` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `username` varchar(50) NOT NULL DEFAULT '' COMMENT 'username',
  `password` varchar(50) NOT NULL DEFAULT '' COMMENT 'password',
  `sex` tinyint(4) NOT NULL DEFAULT '0' COMMENT 'Sex 0=female 1=male ',
  `deleted` tinyint(4) unsigned NOT NULL DEFAULT '0' COMMENT 'Delete flag, default 0 not delete, 1 delete',
  `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'update time',
  `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'create time',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT='user table';

use docker;

Separate redis container instance

docker run -p 6399:6379 --name redis608 --privileged=true -v /app/redis/redis.conf:/etc/redis/redis.conf -v /app/redis/data:/data - d redis:6.0.8 redis-server /etc/redis/redis.conf

Modify application.properties

Package and upload to the Linux virtual machine

Write DockerFile

# The basic image uses java
FROM java:8
# author
MAINTAINER zk
# VOLUME specifies the temporary file directory as /tmp, creates a temporary file in the host /var/lib/docker directory and links it to the container's /tmp
VOLUME /tmp
# Add the jar package to the container and rename it to zk_docker.jar
ADD DockerDemo-1.0-SNAPSHOT.jar zk_docker.jar
# Run the jar package
RUN bash -c 'touch /zk_docker.jar'
ENTRYPOINT ["java","-jar","/zk_docker.jar"]
#Expose port 6001 as a microservice
EXPOSE 6001

Compile the dockerFile file and specify to package it into a specified version of the image

docker build -f docker_demo -t zk_docker:1.8 .

Query Mirror

docker images

# Expose the port externally
docker run -d -p 6001:6001 container ID

docker ps

Run successfully!

Enter browser test

http://ip:6001/user/find/88

http://ip:6001/swagger-ui.html

Using Compose to deploy

Write docker-compose.yml file

version: "3"

services:
  microService:
    image: zk_docker:1.9
    container_name: ms01
    ports:
      - "6001:6001"
    volumes:
      - /app/microService:/data
    networks:
      - zk_net
    depends_on:
      -redis
      -mysql

  redis:
    image: redis:6.0.8
    ports:
      - "6399:6379"
    volumes:
      - /app/redis/redis.conf:/etc/redis/redis.conf
      - /app/redis/data:/data
    networks:
      - zk_net
    command: redis-server /etc/redis/redis.conf

  mysql:
    image: mysql:5.7
    container_name: mysql
    environment:
      MYSQL_ROOT_PASSWORD: 'root'
      MYSQL_ALLOW_EMPTY_PASSWORD: 'no'
      MYSQL_DATABASE: 'docker'
      MYSQL_USER: 'zzkk'
      MYSQL_PASSWORD: 'zzkk123'
    ports:
      - "3307:3306"
    volumes:
      - /app/mysql/db:/var/lib/mysql
      - /app/mysql/conf/my.cnf:/etc/my.cnf
      - /app/mysql/init:/docker-entrypoint-initdb.d
    networks:
      - zk_net
    command:
      --default-authentication-plugin=mysql_native_password
      --character-set-server=utf8mb4
      --collation-server=utf8mb4_general_ci
networks:
  zk_net:

Modify application.properties

Create a new folder mydocker

mkdir docker

cd docker

docker build -f docker_demo -t zk_docker:1.9 .

docker-compose up -d

Make sure the 3 files are in the same folder

Modify the dokcerfile file

Now we have 2 versions, one is deployed without DockerCompose and the other is deployed with DockerCompose

One-click deployment of 3 projects

Enter the mysql container instance and create a new library docker + create a new table t_user

docker exec -it container instance id /bin/bash
mysql -uroot -p

create database docker;

use docker;

CREATE TABLE `t_user` (
  `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
  `username` VARCHAR(50) NOT NULL DEFAULT '' COMMENT 'username',
  `password` VARCHAR(50) NOT NULL DEFAULT '' COMMENT 'password',
  `sex` TINYINT(4) NOT NULL DEFAULT '0' COMMENT 'Sex 0=female 1=male ',
  `deleted` TINYINT(4) UNSIGNED NOT NULL DEFAULT '0' COMMENT 'Delete flag, default 0 is not deleted, 1 is deleted',
  `update_time` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'update time',
  `create_time` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'create time',
  PRIMARY KEY (`id`)
) ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='user table';

Test

Query data successfully

Check the log to insert data successfully

docker-compose logs

Redis port 6399 is also connected successfully

Enter the mysql container to view data

Enter the redis container to view data

So far, the process of deploying SpringBoot by Docker has been completed. In the future, you only need to write a DockerFile and docker-compose.yml to achieve one-click deployment!